Add project files.
This commit is contained in:
commit
3087fcea8a
71 files changed
+8325
No files matched your search
@@ -0,0 +1,45 @@
|
||||
---
|
||||
# source/HotReload/include/hot_reload/.clang-tidy
|
||||
#
|
||||
# hot_reload.h itself is Part II (C API) style -- snake_case, per style
|
||||
# guide §13. Copied from the repo-root c-api/.clang-tidy template (see
|
||||
# that file's own header comment for how clang-tidy's directory-scoped
|
||||
# config resolution makes this work); see also
|
||||
# source/HotReload/src/c-api/.clang-tidy for this same override applied
|
||||
# to the C API's .cpp implementation, one directory tree over.
|
||||
InheritParentConfig: true
|
||||
|
||||
Checks: >
|
||||
readability-identifier-naming,
|
||||
bugprone-exception-escape
|
||||
|
||||
CheckOptions:
|
||||
- key: readability-identifier-naming.FunctionCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.VariableCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.ParameterCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.LocalVariableCase
|
||||
value: lower_case
|
||||
|
||||
- key: readability-identifier-naming.StructCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.EnumCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.EnumConstantCase
|
||||
value: UPPER_CASE
|
||||
|
||||
- key: readability-identifier-naming.GlobalConstantCase
|
||||
value: UPPER_CASE
|
||||
- key: readability-identifier-naming.MacroDefinitionCase
|
||||
value: UPPER_CASE
|
||||
|
||||
- key: readability-identifier-naming.GlobalVariableCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.GlobalVariablePrefix
|
||||
value: g_
|
||||
- key: readability-identifier-naming.StaticVariableCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.StaticVariablePrefix
|
||||
value: s_
|
||||
@@ -0,0 +1,292 @@
|
||||
/* hot_reload.h
|
||||
*
|
||||
* Public C API for the hot-reload library -- see cpp-style-guide.md
|
||||
* Part II (C API Style) for the naming/formatting rules this header
|
||||
* follows, and section 11 ("Runtime Loading") for why this boundary is
|
||||
* plain C in the first place: anything crossing a dlopen()/LoadLibrary()
|
||||
* boundary must have C linkage, so this is exactly the kind of surface
|
||||
* that section calls out.
|
||||
*
|
||||
* WHAT THIS LIBRARY DOES
|
||||
* -----------------------
|
||||
* hot_reload_load() loads a shared library (.so/.dll) under a logical
|
||||
* name. hot_reload_reload() loads a NEW build of that same shared
|
||||
* library and, for every function the two builds have in common,
|
||||
* rewrites the FIRST FEW BYTES of the old function in memory into a
|
||||
* jump to the new function -- so every existing call site (anyone who
|
||||
* already resolved the old function's address, including via a plain
|
||||
* C function pointer) transparently starts running the new code,
|
||||
* without restarting the process.
|
||||
*
|
||||
* WHAT THIS LIBRARY DELIBERATELY DOES NOT DO
|
||||
* --------------------------------------------
|
||||
* It never patches anything that isn't executable code. A symbol is
|
||||
* only ever a patch candidate if the loader's own symbol table marks
|
||||
* it as a function (ELF STT_FUNC / a PE export whose address falls
|
||||
* inside an executable section) -- see the design note in
|
||||
* SymbolTable.hpp for exactly how that's determined. Global variables,
|
||||
* class/struct instances, vtables, and anything else classified as
|
||||
* data are always left alone, on both the old and the new module --
|
||||
* there is no code path in this library that ever writes to a data
|
||||
* symbol. This is a mechanical guarantee from how symbols are
|
||||
* classified, not a naming convention you opt into.
|
||||
*
|
||||
* It also cannot verify that a hot-reloadable function's SIGNATURE
|
||||
* hasn't changed. Patching only ever redirects control flow at a fixed
|
||||
* entry point; it has no way to know, from a symbol table alone, that
|
||||
* `void tick(int)` became `void tick(int, int)` between builds. If a
|
||||
* function's signature changes, give it a new exported name (so the
|
||||
* old symbol is treated as removed and the new one as added -- see
|
||||
* HOT_RELOAD_SYMBOL_REMOVED below) rather than reusing the old name.
|
||||
* See the top-level README for the full list of hazards this library
|
||||
* cannot detect for you.
|
||||
*
|
||||
* REQUIREMENTS ON A HOT-RELOADABLE MODULE
|
||||
* ------------------------------------------
|
||||
* Matching an old build's function up with the new build's requires a
|
||||
* name that is stable across recompiles. extern "C" linkage -- see
|
||||
* cpp-style-guide.md section 11 -- is the simplest way to get one: no
|
||||
* mangling at all, so the name never depends on the compiler, its
|
||||
* version, or the function's signature.
|
||||
*
|
||||
* A real, unmodified C++ mangled name works too, and needs no special
|
||||
* handling by this library -- classification only ever looks at a
|
||||
* symbol's type and section, never at what its name looks like, so an
|
||||
* ordinary out-of-line method, a virtual method (redirected wherever
|
||||
* it's reached from -- directly or through a vtable slot, since both
|
||||
* hold the same address), and an operator overload all patch exactly
|
||||
* like any extern "C" function. What extern "C" actually buys you is
|
||||
* a name you can type by hand into hot_reload_get_symbol() and a
|
||||
* guarantee that it won't change if you rebuild with a different
|
||||
* compiler; a mangled name gives up both of those, only stays stable
|
||||
* within one ABI family (GCC and Clang share the Itanium C++ ABI;
|
||||
* MSVC does not), and -- the one hazard that has nothing to do with
|
||||
* this library and everything to do with what "hot-reloadable" can
|
||||
* even mean -- only exists to patch at all if the compiler actually
|
||||
* emitted an out-of-line body for it, which an implicitly-inline
|
||||
* method defined in a class body, or a template instantiated at the
|
||||
* call site, generally will not. See the top-level README's "Hot
|
||||
* reloading C++ symbols" section for the full picture, including how
|
||||
* to get a readable name for logging with hot_reload_demangle().
|
||||
*/
|
||||
#ifndef HOT_RELOAD_H
|
||||
#define HOT_RELOAD_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef HOT_RELOAD_BUILD_SHARED
|
||||
#define HOT_RELOAD_API __declspec(dllexport)
|
||||
#elif defined(HOT_RELOAD_USE_SHARED)
|
||||
#define HOT_RELOAD_API __declspec(dllimport)
|
||||
#else
|
||||
#define HOT_RELOAD_API
|
||||
#endif
|
||||
#else
|
||||
#if defined(HOT_RELOAD_BUILD_SHARED) || defined(HOT_RELOAD_USE_SHARED)
|
||||
#define HOT_RELOAD_API __attribute__((visibility("default")))
|
||||
#else
|
||||
#define HOT_RELOAD_API
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/* Opaque -- see cpp-style-guide.md section 16 ("Do: use opaque
|
||||
* pointers ... to hide implementation details"). The real definition
|
||||
* is the C++ class hot_reload::HotReloadContext (HotReloadContext.hpp),
|
||||
* never visible from this header. */
|
||||
typedef struct HotReloadContext HotReloadContext;
|
||||
|
||||
/* Every fallible function returns one of these -- see section 16
|
||||
* ("Do: return an integer error code / status enum from every
|
||||
* fallible function"). */
|
||||
typedef enum HotReloadStatus
|
||||
{
|
||||
HOT_RELOAD_OK = 0,
|
||||
|
||||
/* ctx, module_name, or path/new_path was NULL, or module_name
|
||||
* was not found for hot_reload_reload / hot_reload_get_symbol. */
|
||||
HOT_RELOAD_ERROR_INVALID_ARGUMENT,
|
||||
|
||||
/* dlopen()/LoadLibrary() (or the equivalent parse of the file on
|
||||
* disk) failed for the given path. On hot_reload_reload(),
|
||||
* this is non-destructive: the previously loaded version is left
|
||||
* running untouched, exactly as if reload had not been called --
|
||||
* see the file comment above HotReloadContext::reload(). Call
|
||||
* hot_reload_last_error() for the platform's own description of
|
||||
* why -- this one status code covers everything from a missing
|
||||
* file to a genuinely corrupt image to (a real case, found while
|
||||
* building this feature) a shared library with an unresolved
|
||||
* symbol reference, and those need very different fixes. */
|
||||
HOT_RELOAD_ERROR_LOAD_FAILED,
|
||||
|
||||
/* hot_reload_reload() was called for a module_name that was never
|
||||
* hot_reload_load()'ed first, so there is no "old" version to
|
||||
* diff against. Call hot_reload_load() once, up front. */
|
||||
HOT_RELOAD_ERROR_NO_PREVIOUS_VERSION,
|
||||
|
||||
/* This process's CPU architecture has no patch backend compiled
|
||||
* in (see Architecture.hpp -- currently x86, x86-64, and ARM64).
|
||||
* Nothing was patched; hot_reload_load() still succeeded, you
|
||||
* just cannot hot_reload_reload() on this target. */
|
||||
HOT_RELOAD_ERROR_UNSUPPORTED_ARCHITECTURE
|
||||
} HotReloadStatus;
|
||||
|
||||
/* Reported once per matched/unmatched symbol during hot_reload_reload()
|
||||
* via HotReloadOptions::on_symbol_event, so the host application can
|
||||
* log (or assert on, in a test) exactly what happened -- nothing here
|
||||
* is silent. */
|
||||
typedef enum HotReloadSymbolEvent
|
||||
{
|
||||
/* Existed in both builds and was classified as code in both --
|
||||
* the old function's entry now jumps to the new one. */
|
||||
HOT_RELOAD_SYMBOL_PATCHED,
|
||||
|
||||
/* Exists in the new build only. Nothing to patch -- it becomes
|
||||
* reachable the normal way, e.g. via hot_reload_get_symbol(). */
|
||||
HOT_RELOAD_SYMBOL_ADDED,
|
||||
|
||||
/* Existed in the old build, missing from the new one. Per
|
||||
* cpp-style-guide.md's spirit of never leaving a caller with an
|
||||
* ambiguous, half-updated state: the old implementation is left
|
||||
* running as-is unless HotReloadOptions::on_deleted_symbol
|
||||
* supplies a stub to redirect to instead -- see that field. */
|
||||
HOT_RELOAD_SYMBOL_REMOVED,
|
||||
|
||||
/* Present in both builds by name, but the symbol table classifies
|
||||
* it as data (or something else non-function) in either build --
|
||||
* e.g. an accidentally-exported global. Never touched, in either
|
||||
* direction. This is the mechanical half of the "only executable
|
||||
* code changes" guarantee described at the top of this file. */
|
||||
HOT_RELOAD_SYMBOL_SKIPPED_NOT_CODE,
|
||||
|
||||
/* Classified as a function in both builds, but the OLD function's
|
||||
* compiled size is smaller than the number of bytes this
|
||||
* platform's jump sequence needs to write -- patching it would
|
||||
* overwrite whatever comes after it in memory. Skipped rather
|
||||
* than guessed; see hot_reload_enable_patchable_functions() in
|
||||
* cmake/HotReload.cmake for how to stop this from happening. */
|
||||
HOT_RELOAD_SYMBOL_SKIPPED_TOO_SMALL,
|
||||
|
||||
/* Classified as a function in both builds, correctly sized, but
|
||||
* the OS refused to make its page temporarily writable (mprotect /
|
||||
* VirtualProtect failed -- e.g. a hardened environment that denies
|
||||
* W^X transitions). Rare in a normal developer setup; reported
|
||||
* rather than silently ignored either way. */
|
||||
HOT_RELOAD_SYMBOL_SKIPPED_UNWRITABLE
|
||||
} HotReloadSymbolEvent;
|
||||
|
||||
/* One event report, passed to HotReloadOptions::on_symbol_event.
|
||||
* old_address/new_address are NULL when not applicable (e.g.
|
||||
* old_address is NULL for HOT_RELOAD_SYMBOL_ADDED). */
|
||||
typedef struct HotReloadSymbolEventInfo
|
||||
{
|
||||
const char* name;
|
||||
HotReloadSymbolEvent kind;
|
||||
void* old_address;
|
||||
void* new_address;
|
||||
} HotReloadSymbolEventInfo;
|
||||
|
||||
typedef void (*HotReloadSymbolEventFn)(const HotReloadSymbolEventInfo* event, void* user_data);
|
||||
|
||||
/* Called instead of patching, once per HOT_RELOAD_SYMBOL_REMOVED
|
||||
* event, so the host can catch stale references during development
|
||||
* instead of them silently continuing to run old code forever. Return
|
||||
* the address of a replacement to redirect the old function to (its
|
||||
* only safe use is a fixed-signature stub -- e.g. `void stub(void)`--
|
||||
* that logs/asserts; this library has no way to know the deleted
|
||||
* function's real signature, so redirecting to anything that reads
|
||||
* arguments the caller didn't pass is undefined behavior, exactly
|
||||
* like calling any function through the wrong function-pointer type).
|
||||
* Return NULL (or pass a NULL callback in HotReloadOptions to begin
|
||||
* with) to leave the old implementation running untouched -- the
|
||||
* default, and the safe choice unless you specifically want deleted
|
||||
* functions to be loud. */
|
||||
typedef void* (*HotReloadDeletedSymbolFn)(const char* symbol_name, void* user_data);
|
||||
|
||||
/* Passed to hot_reload_context_create(); NULL is a valid options
|
||||
* pointer meaning "no callbacks, default policy for everything". */
|
||||
typedef struct HotReloadOptions
|
||||
{
|
||||
HotReloadSymbolEventFn on_symbol_event; /* nullable */
|
||||
HotReloadDeletedSymbolFn on_deleted_symbol; /* nullable */
|
||||
void* user_data; /* passed back to both callbacks, untouched */
|
||||
} HotReloadOptions;
|
||||
|
||||
/* Caller owns the returned pointer and must call
|
||||
* hot_reload_context_destroy() -- see cpp-style-guide.md section 16
|
||||
* ("Document ownership transfer explicitly"). options may be NULL. */
|
||||
HOT_RELOAD_API HotReloadContext* hot_reload_context_create(const HotReloadOptions* options);
|
||||
|
||||
HOT_RELOAD_API void hot_reload_context_destroy(HotReloadContext* ctx);
|
||||
|
||||
/* Loads path for the first time under module_name -- an arbitrary
|
||||
* caller-chosen key used to look the module back up in later
|
||||
* hot_reload_reload()/hot_reload_get_symbol() calls; it does not need
|
||||
* to match anything on disk. Returns HOT_RELOAD_ERROR_INVALID_ARGUMENT
|
||||
* if module_name was already loaded -- call hot_reload_reload()
|
||||
* instead of hot_reload_load() again for the same name. */
|
||||
HOT_RELOAD_API HotReloadStatus hot_reload_load(HotReloadContext* ctx, const char* module_name, const char* path);
|
||||
|
||||
/* Loads a NEW build of the module previously registered under
|
||||
* module_name, diffs its exported functions against the previous
|
||||
* version, and patches every one that is safe to patch in place --
|
||||
* see HotReloadSymbolEvent above for exactly what happens to each
|
||||
* kind of change. On any HOT_RELOAD_ERROR_* return, nothing was
|
||||
* changed: the previous version keeps running exactly as it was. */
|
||||
HOT_RELOAD_API HotReloadStatus hot_reload_reload(HotReloadContext* ctx, const char* module_name, const char* new_path);
|
||||
|
||||
/* Resolves symbol_name in module_name's CURRENT build (i.e. always
|
||||
* the freshest one loaded, patched or not) -- for calling a function
|
||||
* that did not exist yet when the host first started, such as one
|
||||
* reported via HOT_RELOAD_SYMBOL_ADDED. Returns NULL if module_name
|
||||
* is unknown or symbol_name is not an exported function in it. */
|
||||
HOT_RELOAD_API void* hot_reload_get_symbol(HotReloadContext* ctx, const char* module_name, const char* symbol_name);
|
||||
|
||||
/* Only meaningful immediately after hot_reload_load() or
|
||||
* hot_reload_reload() on this SAME ctx returned something other than
|
||||
* HOT_RELOAD_OK: the underlying platform's own description of why --
|
||||
* dlerror() on POSIX, the formatted message for GetLastError() on
|
||||
* Windows -- e.g. "undefined symbol: _ZN6Widget4tickEi" for a shared
|
||||
* library with an unresolved reference (a real, confusing-until-you-
|
||||
* see-this-message case: a non-inline static class member that is
|
||||
* declared but never given an out-of-class definition compiles fine
|
||||
* and links fine into a shared library -- the linker allows a shared
|
||||
* library to keep undefined symbols by default -- and only fails,
|
||||
* right here, once something ODR-uses it and the loader tries to
|
||||
* resolve it for real). Overwritten by the next
|
||||
* hot_reload_load()/hot_reload_reload() call on this ctx; copy it out
|
||||
* immediately if you need to keep it past that point (e.g. for a log
|
||||
* line). Empty if the platform gave no further detail, or if the
|
||||
* failure never reached the platform loader at all (e.g. a NULL
|
||||
* argument, which is HOT_RELOAD_ERROR_INVALID_ARGUMENT before
|
||||
* anything platform-specific happens). A static string internally --
|
||||
* never free() this. */
|
||||
HOT_RELOAD_API const char* hot_reload_last_error(HotReloadContext* ctx);
|
||||
|
||||
/* A static string literal -- never free() this. */
|
||||
HOT_RELOAD_API const char* hot_reload_status_string(HotReloadStatus status);
|
||||
|
||||
/* Renders name for a human to read: a real C++ mangled name (Itanium
|
||||
* on GCC/Clang/MinGW, MSVC's own scheme under MSVC -- whichever this
|
||||
* library itself was built with) becomes something like
|
||||
* "Widget::tick(int)"; anything else, including an ordinary extern "C"
|
||||
* name, comes back unchanged. Safe to call unconditionally on any
|
||||
* HotReloadSymbolEventInfo::name or hot_reload_get_symbol() key --
|
||||
* this never changes what name means for lookup purposes, it is only
|
||||
* ever applied when you ask for it, purely for display. Returns NULL
|
||||
* only if name itself is NULL or allocation fails; caller must free
|
||||
* a non-NULL result with hot_reload_free_string(). */
|
||||
HOT_RELOAD_API char* hot_reload_demangle(const char* name);
|
||||
|
||||
HOT_RELOAD_API void hot_reload_free_string(char* str);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* HOT_RELOAD_H */
|
||||
Reference in new issue
Block a user