[v8] Some pitfalls about memory leaks

1.Persistent.Reset is not a reference

v8 source code:

V8_INLINE Persistent() : PersistentBase<T>(nullptr) {}


template <class T>
void PersistentBase<T>::Reset() {
  if (this->IsEmpty()) return;
  V8::DisposeGlobal(reinterpret_cast<internal::Address*>(this->val_));
  val_ = nullptr;
}


template <class T>
template <class S>
void PersistentBase<T>::Reset(Isolate* isolate, const Local<S>& other) {
  static_assert(std::is_base_of<T, S>::value, "type check");
  Reset();
  if (other.IsEmpty()) return;
  this->val_ = New(isolate, other.val_);
}


template <class T>
template <class S>
void PersistentBase<T>::Reset(Isolate* isolate,
                              const PersistentBase<S>& other) {
  static_assert(std::is_base_of<T, S>::value, "type check");
  Reset();
  if (other.IsEmpty()) return;
  this->val_ = New(isolate, other.val_);
}

So if NewPersistent.Reset(OldPersistent) is used in the code, call OldPersistent.Reset() to release it.

 

2. NewInstance will cause residual Constructor, creating one will generate two, release can only release one.

Nan::HandleScope scope;
  // Prepare constructor template
  Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
  tpl->SetClassName(Nan::New(ClassName).ToLocalChecked());
  tpl->InstanceTemplate()->SetInternalFieldCount(1);
  Nan::SetPrototypeMethod(...);
  Local<Function> cons = tpl->GetFunction();
  return Nan::NewInstance(cons).ToLocalChecked();

 

 

ps: Use the heapdump library kill -USR2 [process number] to generate the heapdump file. The third constructor should correspond to the first one, which is 100, but the result is 200. And when the program performs cleaning work, it can only be released Drop 100. That is to say, every time NewInstance will first generate a piece of data, which is beyond our control. It should be released after the scope of the Local type, but it is not. After that, we use GetReturnValue().Set(js); to enter v8 is a deep copy operation, so two are formed, and the release can only release the later v8.

 

I used the Nan library before, and found that there is a gap between the old and new versions

nan_maybe_43_inl.h:
inline
MaybeLocal<v8::Object> NewInstance(
      v8::Local<v8::Function> h
    , int argc
    , v8::Local<v8::Value> argv[]) {
  v8::Isolate *isolate = v8::Isolate::GetCurrent();
  v8::EscapableHandleScope scope(isolate);
  return scope.Escape(h->NewInstance(isolate->GetCurrentContext(), argc, argv)
                          .FromMaybe(v8::Local<v8::Object>()));
}
 
 
nan_maybe_pre_43_inl.h:
inline
MaybeLocal<v8::Object> NewInstance(
      v8::Local<v8::Function> h
    , int argc
    , v8::Local<v8::Value> argv[]) {
  return MaybeLocal<v8::Object>(h->NewInstance(argc, argv));
}

Among them, the Escape method copies the value in the parameter to an enclosed field, then deletes other local handles, and finally returns a copy of the new handle that can be safely returned.

http://www.360doc.com/content/16/0701/22/832545_572287892.shtml

Under this model, there is a very common pitfall to be aware of: you cannot directly return a local handle from a function that declares a handle scope. If you do this, the native handle you are trying to return will be deleted in the handle scope's destructor before the function returns. The correct way is to use EscapableHandleScope instead of HandleScope to create a handle field, then call the Escape method, and pass in the handle you want to return.

I suspect that it is caused by this operation, including whether there are some problems with the Nan library, so I decided to abandon the Nan library

First try cons->NewInstance(isolate->GetCurrentContext(),3,argv).ToLocalChecked(); it still doesn’t work. I wonder if it’s a problem with Nan’s new method to construct FunctionTemplate.

Local<FunctionTemplate> tpg = Nan::New<FunctionTemplate>(New);

 

On this basis, change the FunctionTemplate into a native method

Local<FunctionTemplate> tpg = Nan::New<FunctionTemplate>(New); -->Local<FunctionTemplate> tpg = FunctionTemplate::New(isolate);

It will be destroyed in advance, resulting in an illegal pointer when used, and the visible scope is still different.

 

Rewrite generateJSInstance, cancel the use of all methods of Nan library, and use v8 native methods to implement

 //Nan::HandleScope scope;
  Isolate *isolate = Isolate::GetCurrent();
  v8::EscapableHandleScope Escope(isolate);
  HandleScope scope(isolate);
  Local<FunctionTemplate> tpg = FunctionTemplate::New(isolate);
  Local<String> v8name=String::NewFromUtf8(isolate, ClassName.c_str(), NewStringType::kInternalized).ToLocalChecked();
  tpg->SetClassName(v8name);
  tpg->InstanceTemplate()->SetInternalFieldCount(1);
  Local<Function> cons = tpg->GetFunction();
  Local<v8::Value> argv[3] = {..., ..., ...};
  Local<Object> js;
  //js = cons->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
  //Escope.Escape没有效果
  //js = Escope.Escape(cons->NewInstance(isolate->GetCurrentContext(),3,argv).FromMaybe(v8::Local<v8::Object>()));
  js = cons->NewInstance(isolate->GetCurrentContext(),3,argv).ToLocalChecked();
  //置成SideEffectType::kHasNoSideEffect没有效果
  //js = cons->NewInstanceWithSideEffectType(isolate->GetCurrentContext(),3,argv,SideEffectType::kHasNoSideEffect).ToLocalChecked();
  args.GetReturnValue().Set(js);
  constructor.Reset();
  return ;

 

 

The result is still invalid.

So the conclusion is that it is still a problem caused by v8's native NewInstance.

NewInstance will call NewInstanceWithSideEffectType

NewInstanceWithSideEffectType source code

MaybeLocal<Object> Function::NewInstanceWithSideEffectType(
    Local<Context> context, int argc, v8::Local<v8::Value> argv[],
    SideEffectType side_effect_type) const {
  auto isolate = reinterpret_cast<i::Isolate*>(context->GetIsolate());
  TRACE_EVENT_CALL_STATS_SCOPED(isolate, "v8", "V8.Execute");
  ENTER_V8(isolate, context, Function, NewInstance, MaybeLocal<Object>(),
           InternalEscapableScope);
  i::TimerEventScope<i::TimerEventExecute> timer_scope(isolate);
  auto self = Utils::OpenHandle(this);
  STATIC_ASSERT(sizeof(v8::Local<v8::Value>) == sizeof(i::Handle<i::Object>));
  bool should_set_has_no_side_effect =
      side_effect_type == SideEffectType::kHasNoSideEffect &&
      isolate->debug_execution_mode() == i::DebugInfo::kSideEffects;
  if (should_set_has_no_side_effect) {
    CHECK(self->IsJSFunction() &&
          i::JSFunction::cast(*self).shared().IsApiFunction());
    i::Object obj =
        i::JSFunction::cast(*self).shared().get_api_func_data().call_code();
    if (obj.IsCallHandlerInfo()) {
      i::CallHandlerInfo handler_info = i::CallHandlerInfo::cast(obj);
      if (!handler_info.IsSideEffectFreeCallHandlerInfo()) {
        handler_info.SetNextCallHasNoSideEffect();
      }
    }
  }
  i::Handle<i::Object>* args = reinterpret_cast<i::Handle<i::Object>*>(argv);
  Local<Object> result;
  has_pending_exception = !ToLocal<Object>(
      i::Execution::New(isolate, self, self, argc, args), &result);
  if (should_set_has_no_side_effect) {
    i::Object obj =
        i::JSFunction::cast(*self).shared().get_api_func_data().call_code();
    if (obj.IsCallHandlerInfo()) {
      i::CallHandlerInfo handler_info = i::CallHandlerInfo::cast(obj);
      if (has_pending_exception) {
        // Restore the map if an exception prevented restoration.
        handler_info.NextCallHasNoSideEffect();
      } else {
        DCHECK(handler_info.IsSideEffectCallHandlerInfo() ||
               handler_info.IsSideEffectFreeCallHandlerInfo());
      }
    }
  }
  RETURN_ON_FAILED_EXECUTION(Object);
  RETURN_ESCAPED(result);
}

Using the native NewInstanceWithSideEffectType to set SideEffectType::kHasNoSideEffect has no effect,

/**
 * Options for marking whether callbacks may trigger JS-observable side effects.
 * Side-effect-free callbacks are allowlisted during debug evaluation with
 * throwOnSideEffect. It applies when calling a Function, FunctionTemplate,
 * or an Accessor callback. For Interceptors, please see
 * PropertyHandlerFlags's kHasNoSideEffect.
 * Callbacks that only cause side effects to the receiver are allowlisted if
 * invoked on receiver objects that are created within the same debug-evaluate
 * call, as these objects are temporary and the side effect does not escape.
 */
enum class SideEffectType {
  kHasSideEffect,
  kHasNoSideEffect,
  kHasSideEffectToReceiver
};

So there is currently no solution to this problem.

 

Guess you like

Origin blog.csdn.net/sm9sun/article/details/107480438