GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: heap_utils.cc Lines: 232 257 90.3 %
Date: 2022-12-07 04:23:16 Branches: 75 114 65.8 %

Line Branch Exec Source
1
#include "diagnosticfilename-inl.h"
2
#include "env-inl.h"
3
#include "memory_tracker-inl.h"
4
#include "node_external_reference.h"
5
#include "stream_base-inl.h"
6
#include "util-inl.h"
7
8
// Copied from https://github.com/nodejs/node/blob/b07dc4d19fdbc15b4f76557dc45b3ce3a43ad0c3/src/util.cc#L36-L41.
9
#ifdef _WIN32
10
#include <io.h>  // _S_IREAD _S_IWRITE
11
#ifndef S_IRUSR
12
#define S_IRUSR _S_IREAD
13
#endif  // S_IRUSR
14
#ifndef S_IWUSR
15
#define S_IWUSR _S_IWRITE
16
#endif  // S_IWUSR
17
#endif
18
19
using v8::Array;
20
using v8::Boolean;
21
using v8::Context;
22
using v8::EmbedderGraph;
23
using v8::EscapableHandleScope;
24
using v8::FunctionCallbackInfo;
25
using v8::FunctionTemplate;
26
using v8::Global;
27
using v8::HandleScope;
28
using v8::HeapProfiler;
29
using v8::HeapSnapshot;
30
using v8::Isolate;
31
using v8::JustVoid;
32
using v8::Local;
33
using v8::Maybe;
34
using v8::MaybeLocal;
35
using v8::Nothing;
36
using v8::Number;
37
using v8::Object;
38
using v8::ObjectTemplate;
39
using v8::String;
40
using v8::Uint8Array;
41
using v8::Value;
42
43
namespace node {
44
namespace heap {
45
46
class JSGraphJSNode : public EmbedderGraph::Node {
47
 public:
48
2599
  const char* Name() override { return "<JS Node>"; }
49
2599
  size_t SizeInBytes() override { return 0; }
50
2599
  bool IsEmbedderNode() override { return false; }
51
7803
  Local<Value> JSValue() { return PersistentToLocal::Strong(persistent_); }
52
53
5200
  int IdentityHash() {
54
5200
    Local<Value> v = JSValue();
55
6224
    if (v->IsObject()) return v.As<Object>()->GetIdentityHash();
56
8352
    if (v->IsName()) return v.As<v8::Name>()->GetIdentityHash();
57
    if (v->IsInt32()) return v.As<v8::Int32>()->Value();
58
    return 0;
59
  }
60
61
2601
  JSGraphJSNode(Isolate* isolate, Local<Value> val)
62
5202
      : persistent_(isolate, val) {
63
2601
    CHECK(!val.IsEmpty());
64
2601
  }
65
66
  struct Hash {
67
5200
    inline size_t operator()(JSGraphJSNode* n) const {
68
5200
      return static_cast<size_t>(n->IdentityHash());
69
    }
70
  };
71
72
  struct Equal {
73
2
    inline bool operator()(JSGraphJSNode* a, JSGraphJSNode* b) const {
74
4
      return a->JSValue()->SameValue(b->JSValue());
75
    }
76
  };
77
78
 private:
79
  Global<Value> persistent_;
80
};
81
82
class JSGraph : public EmbedderGraph {
83
 public:
84
6
  explicit JSGraph(Isolate* isolate) : isolate_(isolate) {}
85
86
2601
  Node* V8Node(const Local<Value>& value) override {
87
5202
    std::unique_ptr<JSGraphJSNode> n { new JSGraphJSNode(isolate_, value) };
88
2601
    auto it = engine_nodes_.find(n.get());
89
2601
    if (it != engine_nodes_.end())
90
2
      return *it;
91
2599
    engine_nodes_.insert(n.get());
92
2599
    return AddNode(std::unique_ptr<Node>(n.release()));
93
  }
94
95
3246
  Node* AddNode(std::unique_ptr<Node> node) override {
96
3246
    Node* n = node.get();
97
3246
    nodes_.emplace(std::move(node));
98
3246
    return n;
99
  }
100
101
3618
  void AddEdge(Node* from, Node* to, const char* name = nullptr) override {
102
7236
    edges_[from].insert(std::make_pair(name, to));
103
3618
  }
104
105
6
  MaybeLocal<Array> CreateObject() const {
106
6
    EscapableHandleScope handle_scope(isolate_);
107
6
    Local<Context> context = isolate_->GetCurrentContext();
108
6
    Environment* env = Environment::GetCurrent(context);
109
110
12
    std::unordered_map<Node*, Local<Object>> info_objects;
111
6
    Local<Array> nodes = Array::New(isolate_, nodes_.size());
112
6
    Local<String> edges_string = FIXED_ONE_BYTE_STRING(isolate_, "edges");
113
6
    Local<String> is_root_string = FIXED_ONE_BYTE_STRING(isolate_, "isRoot");
114
6
    Local<String> name_string = env->name_string();
115
6
    Local<String> size_string = env->size_string();
116
6
    Local<String> value_string = env->value_string();
117
6
    Local<String> wraps_string = FIXED_ONE_BYTE_STRING(isolate_, "wraps");
118
6
    Local<String> to_string = FIXED_ONE_BYTE_STRING(isolate_, "to");
119
120
3252
    for (const std::unique_ptr<Node>& n : nodes_)
121
3246
      info_objects[n.get()] = Object::New(isolate_);
122
123
    {
124
6
      HandleScope handle_scope(isolate_);
125
6
      size_t i = 0;
126
3252
      for (const std::unique_ptr<Node>& n : nodes_) {
127
3246
        Local<Object> obj = info_objects[n.get()];
128
        Local<Value> value;
129
3246
        std::string name_str;
130
3246
        const char* prefix = n->NamePrefix();
131
3246
        if (prefix == nullptr) {
132
2599
          name_str = n->Name();
133
        } else {
134
647
          name_str = n->NamePrefix();
135
647
          name_str += " ";
136
647
          name_str += n->Name();
137
        }
138
3246
        if (!String::NewFromUtf8(isolate_, name_str.c_str()).ToLocal(&value) ||
139
6492
            obj->Set(context, name_string, value).IsNothing() ||
140
3246
            obj->Set(context,
141
                     is_root_string,
142
6492
                     Boolean::New(isolate_, n->IsRootNode()))
143
3246
                .IsNothing() ||
144
3246
            obj->Set(
145
                   context,
146
                   size_string,
147
6492
                   Number::New(isolate_, static_cast<double>(n->SizeInBytes())))
148

9738
                .IsNothing() ||
149

12984
            obj->Set(context, edges_string, Array::New(isolate_)).IsNothing()) {
150
          return MaybeLocal<Array>();
151
        }
152
6492
        if (nodes->Set(context, i++, obj).IsNothing())
153
          return MaybeLocal<Array>();
154
3246
        if (!n->IsEmbedderNode()) {
155
2599
          value = static_cast<JSGraphJSNode*>(n.get())->JSValue();
156
5198
          if (obj->Set(context, value_string, value).IsNothing())
157
            return MaybeLocal<Array>();
158
        }
159
      }
160
    }
161
162
3252
    for (const std::unique_ptr<Node>& n : nodes_) {
163
3246
      Node* wraps = n->WrapperNode();
164
3246
      if (wraps == nullptr) continue;
165
      Local<Object> from = info_objects[n.get()];
166
      Local<Object> to = info_objects[wraps];
167
      if (from->Set(context, wraps_string, to).IsNothing())
168
        return MaybeLocal<Array>();
169
    }
170
171
454
    for (const auto& edge_info : edges_) {
172
448
      Node* source = edge_info.first;
173
      Local<Value> edges;
174

1792
      if (!info_objects[source]->Get(context, edges_string).ToLocal(&edges) ||
175
448
          !edges->IsArray()) {
176
        return MaybeLocal<Array>();
177
      }
178
179
448
      size_t i = 0;
180
448
      size_t j = 0;
181
4066
      for (const auto& edge : edge_info.second) {
182
3618
        Local<Object> to_object = info_objects[edge.second];
183
3618
        Local<Object> edge_obj = Object::New(isolate_);
184
        Local<Value> edge_name_value;
185
3618
        const char* edge_name = edge.first;
186
3618
        if (edge_name != nullptr) {
187
2550
          if (!String::NewFromUtf8(isolate_, edge_name)
188
2550
              .ToLocal(&edge_name_value)) {
189
            return MaybeLocal<Array>();
190
          }
191
        } else {
192
2136
          edge_name_value = Number::New(isolate_, static_cast<double>(j++));
193
        }
194
3618
        if (edge_obj->Set(context, name_string, edge_name_value).IsNothing() ||
195

14472
            edge_obj->Set(context, to_string, to_object).IsNothing() ||
196

10854
            edges.As<Array>()->Set(context, i++, edge_obj).IsNothing()) {
197
          return MaybeLocal<Array>();
198
        }
199
      }
200
    }
201
202
6
    return handle_scope.Escape(nodes);
203
  }
204
205
 private:
206
  Isolate* isolate_;
207
  std::unordered_set<std::unique_ptr<Node>> nodes_;
208
  std::unordered_set<JSGraphJSNode*, JSGraphJSNode::Hash, JSGraphJSNode::Equal>
209
      engine_nodes_;
210
  std::unordered_map<Node*, std::set<std::pair<const char*, Node*>>> edges_;
211
};
212
213
6
void BuildEmbedderGraph(const FunctionCallbackInfo<Value>& args) {
214
6
  Environment* env = Environment::GetCurrent(args);
215
12
  JSGraph graph(env->isolate());
216
6
  Environment::BuildEmbedderGraph(env->isolate(), &graph, env);
217
  Local<Array> ret;
218
12
  if (graph.CreateObject().ToLocal(&ret))
219
12
    args.GetReturnValue().Set(ret);
220
6
}
221
222
namespace {
223
class FileOutputStream : public v8::OutputStream {
224
 public:
225
8
  FileOutputStream(const int fd, uv_fs_t* req) : fd_(fd), req_(req) {}
226
227
16
  int GetChunkSize() override {
228
16
    return 65536;  // big chunks == faster
229
  }
230
231
8
  void EndOfStream() override {}
232
233
669
  WriteResult WriteAsciiChunk(char* data, const int size) override {
234
    DCHECK_EQ(status_, 0);
235
669
    int offset = 0;
236
1338
    while (offset < size) {
237
669
      const uv_buf_t buf = uv_buf_init(data + offset, size - offset);
238
669
      const int num_bytes_written = uv_fs_write(nullptr,
239
                                                req_,
240
669
                                                fd_,
241
                                                &buf,
242
                                                1,
243
                                                -1,
244
                                                nullptr);
245
669
      uv_fs_req_cleanup(req_);
246
669
      if (num_bytes_written < 0) {
247
        status_ = num_bytes_written;
248
        return kAbort;
249
      }
250
      DCHECK_LE(static_cast<size_t>(num_bytes_written), buf.len);
251
669
      offset += num_bytes_written;
252
    }
253
    DCHECK_EQ(offset, size);
254
669
    return kContinue;
255
  }
256
257
8
  int status() const { return status_; }
258
259
 private:
260
  const int fd_;
261
  uv_fs_t* req_;
262
  int status_ = 0;
263
};
264
265
class HeapSnapshotStream : public AsyncWrap,
266
                           public StreamBase,
267
                           public v8::OutputStream {
268
 public:
269
20
  HeapSnapshotStream(
270
      Environment* env,
271
      HeapSnapshotPointer&& snapshot,
272
20
      Local<Object> obj) :
273
      AsyncWrap(env, obj, AsyncWrap::PROVIDER_HEAPSNAPSHOT),
274
      StreamBase(env),
275
20
      snapshot_(std::move(snapshot)) {
276
20
    MakeWeak();
277
20
    StreamBase::AttachToObject(GetObject());
278
20
  }
279
280
80
  ~HeapSnapshotStream() override {}
281
282
34
  int GetChunkSize() override {
283
34
    return 65536;  // big chunks == faster
284
  }
285
286
17
  void EndOfStream() override {
287
17
    EmitRead(UV_EOF);
288
    snapshot_.reset();
289
17
  }
290
291
1415
  WriteResult WriteAsciiChunk(char* data, int size) override {
292
1415
    int len = size;
293
2830
    while (len != 0) {
294
1415
      uv_buf_t buf = EmitAlloc(size);
295
1415
      ssize_t avail = len;
296
1415
      if (static_cast<ssize_t>(buf.len) < avail)
297
        avail = buf.len;
298
1415
      memcpy(buf.base, data, avail);
299
1415
      data += avail;
300
1415
      len -= static_cast<int>(avail);
301
1415
      EmitRead(size, buf);
302
    }
303
1415
    return kContinue;
304
  }
305
306
17
  int ReadStart() override {
307
17
    CHECK_NE(snapshot_, nullptr);
308
17
    snapshot_->Serialize(this, HeapSnapshot::kJSON);
309
17
    return 0;
310
  }
311
312
1415
  int ReadStop() override {
313
1415
    return 0;
314
  }
315
316
  int DoShutdown(ShutdownWrap* req_wrap) override {
317
    UNREACHABLE();
318
  }
319
320
  int DoWrite(WriteWrap* w,
321
              uv_buf_t* bufs,
322
              size_t count,
323
              uv_stream_t* send_handle) override {
324
    UNREACHABLE();
325
  }
326
327
1432
  bool IsAlive() override { return snapshot_ != nullptr; }
328
  bool IsClosing() override { return snapshot_ == nullptr; }
329
2884
  AsyncWrap* GetAsyncWrap() override { return this; }
330
331
12
  void MemoryInfo(MemoryTracker* tracker) const override {
332
12
    if (snapshot_ != nullptr) {
333
7
      tracker->TrackFieldWithSize(
334
          "snapshot", sizeof(*snapshot_), "HeapSnapshot");
335
    }
336
12
  }
337
338
12
  SET_MEMORY_INFO_NAME(HeapSnapshotStream)
339
12
  SET_SELF_SIZE(HeapSnapshotStream)
340
341
 private:
342
  HeapSnapshotPointer snapshot_;
343
};
344
345
8
inline void TakeSnapshot(Environment* env,
346
                         v8::OutputStream* out,
347
                         HeapProfiler::HeapSnapshotOptions options) {
348
  HeapSnapshotPointer snapshot{
349
16
      env->isolate()->GetHeapProfiler()->TakeHeapSnapshot(options)};
350
8
  snapshot->Serialize(out, HeapSnapshot::kJSON);
351
8
}
352
353
}  // namespace
354
355
9
Maybe<void> WriteSnapshot(Environment* env,
356
                          const char* filename,
357
                          HeapProfiler::HeapSnapshotOptions options) {
358
  uv_fs_t req;
359
  int err;
360
361
9
  const int fd = uv_fs_open(nullptr,
362
                            &req,
363
                            filename,
364
                            O_WRONLY | O_CREAT | O_TRUNC,
365
                            S_IWUSR | S_IRUSR,
366
                            nullptr);
367
9
  uv_fs_req_cleanup(&req);
368
9
  if ((err = fd) < 0) {
369
1
    env->ThrowUVException(err, "open", nullptr, filename);
370
1
    return Nothing<void>();
371
  }
372
373
16
  FileOutputStream stream(fd, &req);
374
8
  TakeSnapshot(env, &stream, options);
375
8
  if ((err = stream.status()) < 0) {
376
    env->ThrowUVException(err, "write", nullptr, filename);
377
    return Nothing<void>();
378
  }
379
380
8
  err = uv_fs_close(nullptr, &req, fd, nullptr);
381
8
  uv_fs_req_cleanup(&req);
382
8
  if (err < 0) {
383
    env->ThrowUVException(err, "close", nullptr, filename);
384
    return Nothing<void>();
385
  }
386
387
8
  return JustVoid();
388
}
389
390
28
void DeleteHeapSnapshot(const HeapSnapshot* snapshot) {
391
28
  const_cast<HeapSnapshot*>(snapshot)->Delete();
392
28
}
393
394
20
BaseObjectPtr<AsyncWrap> CreateHeapSnapshotStream(
395
    Environment* env, HeapSnapshotPointer&& snapshot) {
396
40
  HandleScope scope(env->isolate());
397
398
40
  if (env->streambaseoutputstream_constructor_template().IsEmpty()) {
399
    // Create FunctionTemplate for HeapSnapshotStream
400
10
    Local<FunctionTemplate> os = FunctionTemplate::New(env->isolate());
401
10
    os->Inherit(AsyncWrap::GetConstructorTemplate(env));
402
10
    Local<ObjectTemplate> ost = os->InstanceTemplate();
403
10
    ost->SetInternalFieldCount(StreamBase::kInternalFieldCount);
404
10
    os->SetClassName(
405
        FIXED_ONE_BYTE_STRING(env->isolate(), "HeapSnapshotStream"));
406
10
    StreamBase::AddMethods(env, os);
407
10
    env->set_streambaseoutputstream_constructor_template(ost);
408
  }
409
410
  Local<Object> obj;
411
20
  if (!env->streambaseoutputstream_constructor_template()
412
20
           ->NewInstance(env->context())
413
20
           .ToLocal(&obj)) {
414
    return {};
415
  }
416
20
  return MakeBaseObject<HeapSnapshotStream>(env, std::move(snapshot), obj);
417
}
418
419
27
HeapProfiler::HeapSnapshotOptions GetHeapSnapshotOptions(
420
    Local<Value> options_value) {
421
27
  CHECK(options_value->IsUint8Array());
422
27
  Local<Uint8Array> arr = options_value.As<Uint8Array>();
423
  uint8_t* options =
424
81
      static_cast<uint8_t*>(arr->Buffer()->Data()) + arr->ByteOffset();
425
27
  HeapProfiler::HeapSnapshotOptions result;
426
54
  result.snapshot_mode = options[0]
427
27
                             ? HeapProfiler::HeapSnapshotMode::kExposeInternals
428
                             : HeapProfiler::HeapSnapshotMode::kRegular;
429
54
  result.numerics_mode = options[1]
430
27
                             ? HeapProfiler::NumericsMode::kExposeNumericValues
431
                             : HeapProfiler::NumericsMode::kHideNumericValues;
432
27
  return result;
433
}
434
435
17
void CreateHeapSnapshotStream(const FunctionCallbackInfo<Value>& args) {
436
17
  Environment* env = Environment::GetCurrent(args);
437
17
  CHECK_EQ(args.Length(), 1);
438
17
  auto options = GetHeapSnapshotOptions(args[0]);
439
  HeapSnapshotPointer snapshot{
440
34
      env->isolate()->GetHeapProfiler()->TakeHeapSnapshot(options)};
441
17
  CHECK(snapshot);
442
  BaseObjectPtr<AsyncWrap> stream =
443
34
      CreateHeapSnapshotStream(env, std::move(snapshot));
444
17
  if (stream)
445
34
    args.GetReturnValue().Set(stream->object());
446
17
}
447
448
7
void TriggerHeapSnapshot(const FunctionCallbackInfo<Value>& args) {
449
7
  Environment* env = Environment::GetCurrent(args);
450
7
  Isolate* isolate = args.GetIsolate();
451
7
  CHECK_EQ(args.Length(), 2);
452
7
  Local<Value> filename_v = args[0];
453
7
  auto options = GetHeapSnapshotOptions(args[1]);
454
455
14
  if (filename_v->IsUndefined()) {
456
10
    DiagnosticFilename name(env, "Heap", "heapsnapshot");
457
10
    if (WriteSnapshot(env, *name, options).IsNothing()) return;
458
10
    if (String::NewFromUtf8(isolate, *name).ToLocal(&filename_v)) {
459
10
      args.GetReturnValue().Set(filename_v);
460
    }
461
5
    return;
462
  }
463
464
4
  BufferValue path(isolate, filename_v);
465
2
  CHECK_NOT_NULL(*path);
466
4
  if (WriteSnapshot(env, *path, options).IsNothing()) return;
467
2
  return args.GetReturnValue().Set(filename_v);
468
}
469
470
800
void Initialize(Local<Object> target,
471
                Local<Value> unused,
472
                Local<Context> context,
473
                void* priv) {
474
800
  SetMethod(context, target, "buildEmbedderGraph", BuildEmbedderGraph);
475
800
  SetMethod(context, target, "triggerHeapSnapshot", TriggerHeapSnapshot);
476
800
  SetMethod(
477
      context, target, "createHeapSnapshotStream", CreateHeapSnapshotStream);
478
800
}
479
480
5639
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
481
5639
  registry->Register(BuildEmbedderGraph);
482
5639
  registry->Register(TriggerHeapSnapshot);
483
5639
  registry->Register(CreateHeapSnapshotStream);
484
5639
}
485
486
}  // namespace heap
487
}  // namespace node
488
489
5710
NODE_BINDING_CONTEXT_AWARE_INTERNAL(heap_utils, node::heap::Initialize)
490
5639
NODE_BINDING_EXTERNAL_REFERENCE(heap_utils,
491
                                node::heap::RegisterExternalReferences)