Files
node/src/node_contextify.h
T
Anna Henningsen 40364b181d src: add check against non-weak BaseObjects at process exit
When a process exits cleanly, i.e. because the event loop ends up
without things to wait for, the Node.js objects that are left on
the heap should be:

 1. weak, i.e. ready for garbage collection once no longer
    referenced, or
 2. detached, i.e. scheduled for destruction once no longer
    referenced, or
 3. an unrefed libuv handle, i.e. does not keep the event loop
    alive, or
 4. an inactive libuv handle (essentially the same here)

There are a few exceptions to this rule, but generally,
if there are C++-backed Node.js objects on the heap
that do not fall into the above categories, we may be looking
at a potential memory leak. Most likely, the cause is a missing
`MakeWeak()` call on the corresponding object.

In order to avoid this kind of problem, we check the list
of BaseObjects for these criteria. In this commit, we only do so
when explicitly instructed to or when in debug mode
(where --verify-base-objects is always-on).

In particular, this avoids the kinds of memory leak issues
that were fixed in the PRs referenced below.

Refs: https://github.com/nodejs/node/pull/35488
Refs: https://github.com/nodejs/node/pull/35487
Refs: https://github.com/nodejs/node/pull/35481

PR-URL: https://github.com/nodejs/node/pull/35490
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
2020-10-07 10:54:34 +02:00

192 lines
6.4 KiB
C++

#ifndef SRC_NODE_CONTEXTIFY_H_
#define SRC_NODE_CONTEXTIFY_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object-inl.h"
#include "node_context_data.h"
#include "node_errors.h"
namespace node {
namespace contextify {
class MicrotaskQueueWrap : public BaseObject {
public:
MicrotaskQueueWrap(Environment* env, v8::Local<v8::Object> obj);
const std::shared_ptr<v8::MicrotaskQueue>& microtask_queue() const;
static void Init(Environment* env, v8::Local<v8::Object> target);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
// This could have methods for running the microtask queue, if we ever decide
// to make that fully customizable from userland.
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(MicrotaskQueueWrap)
SET_SELF_SIZE(MicrotaskQueueWrap)
private:
std::shared_ptr<v8::MicrotaskQueue> microtask_queue_;
};
struct ContextOptions {
v8::Local<v8::String> name;
v8::Local<v8::String> origin;
v8::Local<v8::Boolean> allow_code_gen_strings;
v8::Local<v8::Boolean> allow_code_gen_wasm;
BaseObjectPtr<MicrotaskQueueWrap> microtask_queue_wrap;
};
class ContextifyContext {
public:
enum InternalFields { kSlot, kInternalFieldCount };
ContextifyContext(Environment* env,
v8::Local<v8::Object> sandbox_obj,
const ContextOptions& options);
~ContextifyContext();
static void CleanupHook(void* arg);
v8::MaybeLocal<v8::Object> CreateDataWrapper(Environment* env);
v8::MaybeLocal<v8::Context> CreateV8Context(Environment* env,
v8::Local<v8::Object> sandbox_obj,
const ContextOptions& options);
static void Init(Environment* env, v8::Local<v8::Object> target);
static ContextifyContext* ContextFromContextifiedSandbox(
Environment* env,
const v8::Local<v8::Object>& sandbox);
inline Environment* env() const {
return env_;
}
inline v8::Local<v8::Context> context() const {
return PersistentToLocal::Default(env()->isolate(), context_);
}
inline v8::Local<v8::Object> global_proxy() const {
return context()->Global();
}
inline v8::Local<v8::Object> sandbox() const {
return v8::Local<v8::Object>::Cast(
context()->GetEmbedderData(ContextEmbedderIndex::kSandboxObject));
}
inline std::shared_ptr<v8::MicrotaskQueue> microtask_queue() const {
if (!microtask_queue_wrap_) return {};
return microtask_queue_wrap_->microtask_queue();
}
template <typename T>
static ContextifyContext* Get(const v8::PropertyCallbackInfo<T>& args);
private:
static void MakeContext(const v8::FunctionCallbackInfo<v8::Value>& args);
static void IsContext(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CompileFunction(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void WeakCallback(
const v8::WeakCallbackInfo<ContextifyContext>& data);
static void PropertyGetterCallback(
v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& args);
static void PropertySetterCallback(
v8::Local<v8::Name> property,
v8::Local<v8::Value> value,
const v8::PropertyCallbackInfo<v8::Value>& args);
static void PropertyDescriptorCallback(
v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Value>& args);
static void PropertyDefinerCallback(
v8::Local<v8::Name> property,
const v8::PropertyDescriptor& desc,
const v8::PropertyCallbackInfo<v8::Value>& args);
static void PropertyDeleterCallback(
v8::Local<v8::Name> property,
const v8::PropertyCallbackInfo<v8::Boolean>& args);
static void PropertyEnumeratorCallback(
const v8::PropertyCallbackInfo<v8::Array>& args);
static void IndexedPropertyGetterCallback(
uint32_t index,
const v8::PropertyCallbackInfo<v8::Value>& args);
static void IndexedPropertySetterCallback(
uint32_t index,
v8::Local<v8::Value> value,
const v8::PropertyCallbackInfo<v8::Value>& args);
static void IndexedPropertyDescriptorCallback(
uint32_t index,
const v8::PropertyCallbackInfo<v8::Value>& args);
static void IndexedPropertyDefinerCallback(
uint32_t index,
const v8::PropertyDescriptor& desc,
const v8::PropertyCallbackInfo<v8::Value>& args);
static void IndexedPropertyDeleterCallback(
uint32_t index,
const v8::PropertyCallbackInfo<v8::Boolean>& args);
Environment* const env_;
v8::Global<v8::Context> context_;
BaseObjectPtr<MicrotaskQueueWrap> microtask_queue_wrap_;
};
class ContextifyScript : public BaseObject {
public:
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(ContextifyScript)
SET_SELF_SIZE(ContextifyScript)
ContextifyScript(Environment* env, v8::Local<v8::Object> object);
~ContextifyScript() override;
static void Init(Environment* env, v8::Local<v8::Object> target);
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
static bool InstanceOf(Environment* env, const v8::Local<v8::Value>& args);
static void CreateCachedData(
const v8::FunctionCallbackInfo<v8::Value>& args);
static void RunInThisContext(const v8::FunctionCallbackInfo<v8::Value>& args);
static void RunInContext(const v8::FunctionCallbackInfo<v8::Value>& args);
static bool EvalMachine(Environment* env,
const int64_t timeout,
const bool display_errors,
const bool break_on_sigint,
const bool break_on_first_line,
std::shared_ptr<v8::MicrotaskQueue> microtask_queue,
const v8::FunctionCallbackInfo<v8::Value>& args);
inline uint32_t id() { return id_; }
private:
v8::Global<v8::UnboundScript> script_;
uint32_t id_;
};
class CompiledFnEntry final : public BaseObject {
public:
SET_NO_MEMORY_INFO()
SET_MEMORY_INFO_NAME(CompiledFnEntry)
SET_SELF_SIZE(CompiledFnEntry)
CompiledFnEntry(Environment* env,
v8::Local<v8::Object> object,
uint32_t id,
v8::Local<v8::ScriptOrModule> script);
~CompiledFnEntry();
bool IsNotIndicativeOfMemoryLeakAtExit() const override { return true; }
private:
uint32_t id_;
v8::Global<v8::ScriptOrModule> script_;
static void WeakCallback(const v8::WeakCallbackInfo<CompiledFnEntry>& data);
};
} // namespace contextify
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_NODE_CONTEXTIFY_H_