GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_snapshotable.cc Lines: 184 241 76.3 %
Date: 2022-07-21 04:16:21 Branches: 64 118 54.2 %

Line Branch Exec Source
1
2
#include "node_snapshotable.h"
3
#include <iostream>
4
#include <sstream>
5
#include "base_object-inl.h"
6
#include "debug_utils-inl.h"
7
#include "env-inl.h"
8
#include "node_blob.h"
9
#include "node_errors.h"
10
#include "node_external_reference.h"
11
#include "node_file.h"
12
#include "node_internals.h"
13
#include "node_main_instance.h"
14
#include "node_native_module.h"
15
#include "node_process.h"
16
#include "node_snapshot_builder.h"
17
#include "node_v8.h"
18
#include "node_v8_platform-inl.h"
19
20
#if HAVE_INSPECTOR
21
#include "inspector/worker_inspector.h"  // ParentInspectorHandle
22
#endif
23
24
namespace node {
25
26
using v8::Context;
27
using v8::Function;
28
using v8::FunctionCallbackInfo;
29
using v8::HandleScope;
30
using v8::Isolate;
31
using v8::Local;
32
using v8::Object;
33
using v8::ScriptCompiler;
34
using v8::ScriptOrigin;
35
using v8::SnapshotCreator;
36
using v8::StartupData;
37
using v8::String;
38
using v8::TryCatch;
39
using v8::Value;
40
41
5325
SnapshotData::~SnapshotData() {
42
5325
  if (data_ownership == DataOwnership::kOwned &&
43
6
      v8_snapshot_blob_data.data != nullptr) {
44
6
    delete[] v8_snapshot_blob_data.data;
45
  }
46
5325
}
47
48
template <typename T>
49
3192
void WriteVector(std::ostream* ss, const T* vec, size_t size) {
50
23819686
  for (size_t i = 0; i < size; i++) {
51
23816494
    *ss << std::to_string(vec[i]) << (i == size - 1 ? '\n' : ',');
52
  }
53
3192
}
54
55
3168
static std::string GetCodeCacheDefName(const std::string& id) {
56
3168
  char buf[64] = {0};
57
3168
  size_t size = id.size();
58
3168
  CHECK_LT(size, sizeof(buf));
59
68988
  for (size_t i = 0; i < size; ++i) {
60
65820
    char ch = id[i];
61

65820
    buf[i] = (ch == '-' || ch == '/') ? '_' : ch;
62
  }
63
3168
  return std::string(buf) + std::string("_cache_data");
64
}
65
66
1584
static std::string FormatSize(size_t size) {
67
1584
  char buf[64] = {0};
68
1584
  if (size < 1024) {
69
126
    snprintf(buf, sizeof(buf), "%.2fB", static_cast<double>(size));
70
1458
  } else if (size < 1024 * 1024) {
71
1458
    snprintf(buf, sizeof(buf), "%.2fKB", static_cast<double>(size / 1024));
72
  } else {
73
    snprintf(
74
        buf, sizeof(buf), "%.2fMB", static_cast<double>(size / 1024 / 1024));
75
  }
76
1584
  return buf;
77
}
78
79
1584
static void WriteStaticCodeCacheData(std::ostream* ss,
80
                                     const native_module::CodeCacheInfo& info) {
81
1584
  *ss << "static const uint8_t " << GetCodeCacheDefName(info.id) << "[] = {\n";
82
1584
  WriteVector(ss, info.data.data(), info.data.size());
83
1584
  *ss << "};";
84
1584
}
85
86
1584
static void WriteCodeCacheInitializer(std::ostream* ss, const std::string& id) {
87
3168
  std::string def_name = GetCodeCacheDefName(id);
88
1584
  *ss << "    { \"" << id << "\",\n";
89
1584
  *ss << "      {" << def_name << ",\n";
90
1584
  *ss << "       " << def_name << " + arraysize(" << def_name << "),\n";
91
1584
  *ss << "      }\n";
92
1584
  *ss << "    },\n";
93
1584
}
94
95
6
void FormatBlob(std::ostream& ss, SnapshotData* data) {
96
6
  ss << R"(#include <cstddef>
97
#include "env.h"
98
#include "node_snapshot_builder.h"
99
#include "v8.h"
100
101
// This file is generated by tools/snapshot. Do not edit.
102
103
namespace node {
104
105
static const char v8_snapshot_blob_data[] = {
106
)";
107
6
  WriteVector(&ss,
108
              data->v8_snapshot_blob_data.data,
109
6
              data->v8_snapshot_blob_data.raw_size);
110
6
  ss << R"(};
111
112
static const int v8_snapshot_blob_size = )"
113
6
     << data->v8_snapshot_blob_data.raw_size << ";";
114
115
  // Windows can't deal with too many large vector initializers.
116
  // Store the data into static arrays first.
117
1590
  for (const auto& item : data->code_cache) {
118
1584
    WriteStaticCodeCacheData(&ss, item);
119
  }
120
121
6
  ss << R"(SnapshotData snapshot_data {
122
  // -- data_ownership begins --
123
  SnapshotData::DataOwnership::kNotOwned,
124
  // -- data_ownership ends --
125
  // -- v8_snapshot_blob_data begins --
126
  { v8_snapshot_blob_data, v8_snapshot_blob_size },
127
  // -- v8_snapshot_blob_data ends --
128
  // -- isolate_data_indices begins --
129
  {
130
)";
131
12
  WriteVector(&ss,
132
6
              data->isolate_data_indices.data(),
133
              data->isolate_data_indices.size());
134
6
  ss << R"(},
135
  // -- isolate_data_indices ends --
136
  // -- env_info begins --
137
6
)" << data->env_info
138
6
     << R"(
139
  // -- env_info ends --
140
  ,
141
  // -- code_cache begins --
142
  {)";
143
1590
  for (const auto& item : data->code_cache) {
144
1584
    WriteCodeCacheInitializer(&ss, item.id);
145
  }
146
6
  ss << R"(
147
  }
148
  // -- code_cache ends --
149
};
150
151
const SnapshotData* SnapshotBuilder::GetEmbeddedSnapshotData() {
152
  Mutex::ScopedLock lock(snapshot_data_mutex_);
153
  return &snapshot_data;
154
}
155
}  // namespace node
156
)";
157
6
}
158
159
Mutex SnapshotBuilder::snapshot_data_mutex_;
160
161
6521
const std::vector<intptr_t>& SnapshotBuilder::CollectExternalReferences() {
162

6521
  static auto registry = std::make_unique<ExternalReferenceRegistry>();
163
6521
  return registry->external_references();
164
}
165
166
6515
void SnapshotBuilder::InitializeIsolateParams(const SnapshotData* data,
167
                                              Isolate::CreateParams* params) {
168
6515
  params->external_references = CollectExternalReferences().data();
169
6515
  params->snapshot_blob =
170
6515
      const_cast<v8::StartupData*>(&(data->v8_snapshot_blob_data));
171
6515
}
172
173
// TODO(joyeecheung): share these exit code constants across the code base.
174
constexpr int UNCAUGHT_EXCEPTION_ERROR = 1;
175
constexpr int BOOTSTRAP_ERROR = 10;
176
constexpr int SNAPSHOT_ERROR = 14;
177
178
6
int SnapshotBuilder::Generate(SnapshotData* out,
179
                              const std::vector<std::string> args,
180
                              const std::vector<std::string> exec_args) {
181
  const std::vector<intptr_t>& external_references =
182
6
      CollectExternalReferences();
183
6
  Isolate* isolate = Isolate::Allocate();
184
  // Must be done before the SnapshotCreator creation so  that the
185
  // memory reducer can be initialized.
186
12
  per_process::v8_platform.Platform()->RegisterIsolate(isolate,
187
6
                                                       uv_default_loop());
188
189
12
  SnapshotCreator creator(isolate, external_references.data());
190
191
6
  isolate->SetCaptureStackTraceForUncaughtExceptions(
192
      true, 10, v8::StackTrace::StackTraceOptions::kDetailed);
193
194
6
  Environment* env = nullptr;
195
  std::unique_ptr<NodeMainInstance> main_instance =
196
      NodeMainInstance::Create(isolate,
197
                               uv_default_loop(),
198
6
                               per_process::v8_platform.Platform(),
199
                               args,
200
12
                               exec_args);
201
202
  // The cleanups should be done in case of an early exit due to errors.
203
6
  auto cleanup = OnScopeLeave([&]() {
204
    // Must be done while the snapshot creator isolate is entered i.e. the
205
    // creator is still alive. The snapshot creator destructor will destroy
206
    // the isolate.
207
6
    if (env != nullptr) {
208
6
      FreeEnvironment(env);
209
    }
210
6
    main_instance->Dispose();
211
6
    per_process::v8_platform.Platform()->UnregisterIsolate(isolate);
212
12
  });
213
214
  {
215
6
    HandleScope scope(isolate);
216
6
    TryCatch bootstrapCatch(isolate);
217
218
6
    auto print_Exception = OnScopeLeave([&]() {
219
6
      if (bootstrapCatch.HasCaught()) {
220
        PrintCaughtException(
221
            isolate, isolate->GetCurrentContext(), bootstrapCatch);
222
      }
223
6
    });
224
225
    out->isolate_data_indices =
226
6
        main_instance->isolate_data()->Serialize(&creator);
227
228
    // The default context with only things created by V8.
229
6
    Local<Context> default_context = Context::New(isolate);
230
231
    // The Node.js-specific context with primodials, can be used by workers
232
    // TODO(joyeecheung): investigate if this can be used by vm contexts
233
    // without breaking compatibility.
234
6
    Local<Context> base_context = NewContext(isolate);
235
6
    if (base_context.IsEmpty()) {
236
      return BOOTSTRAP_ERROR;
237
    }
238
239
6
    Local<Context> main_context = NewContext(isolate);
240
6
    if (main_context.IsEmpty()) {
241
      return BOOTSTRAP_ERROR;
242
    }
243
    // Initialize the main instance context.
244
    {
245
6
      Context::Scope context_scope(main_context);
246
247
      // Create the environment.
248
6
      env = new Environment(main_instance->isolate_data(),
249
                            main_context,
250
                            args,
251
                            exec_args,
252
                            nullptr,
253
                            node::EnvironmentFlags::kDefaultFlags,
254
6
                            {});
255
256
      // Run scripts in lib/internal/bootstrap/
257
12
      if (env->RunBootstrapping().IsEmpty()) {
258
        return BOOTSTRAP_ERROR;
259
      }
260
      // If --build-snapshot is true, lib/internal/main/mksnapshot.js would be
261
      // loaded via LoadEnvironment() to execute process.argv[1] as the entry
262
      // point (we currently only support this kind of entry point, but we
263
      // could also explore snapshotting other kinds of execution modes
264
      // in the future).
265
6
      if (per_process::cli_options->build_snapshot) {
266
#if HAVE_INSPECTOR
267
        // TODO(joyeecheung): move this before RunBootstrapping().
268
        env->InitializeInspector({});
269
#endif
270
        if (LoadEnvironment(env, StartExecutionCallback{}).IsEmpty()) {
271
          return UNCAUGHT_EXCEPTION_ERROR;
272
        }
273
        // FIXME(joyeecheung): right now running the loop in the snapshot
274
        // builder seems to introduces inconsistencies in JS land that need to
275
        // be synchronized again after snapshot restoration.
276
        int exit_code = SpinEventLoop(env).FromMaybe(UNCAUGHT_EXCEPTION_ERROR);
277
        if (exit_code != 0) {
278
          return exit_code;
279
        }
280
      }
281
282
6
      if (per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
283
        env->PrintAllBaseObjects();
284
        printf("Environment = %p\n", env);
285
      }
286
287
      // Serialize the native states
288
6
      out->env_info = env->Serialize(&creator);
289
290
#ifdef NODE_USE_NODE_CODE_CACHE
291
      // Regenerate all the code cache.
292
6
      if (!native_module::NativeModuleLoader::CompileAllModules(main_context)) {
293
        return UNCAUGHT_EXCEPTION_ERROR;
294
      }
295
6
      native_module::NativeModuleLoader::CopyCodeCache(&(out->code_cache));
296
1590
      for (const auto& item : out->code_cache) {
297
1584
        std::string size_str = FormatSize(item.data.size());
298
        per_process::Debug(DebugCategory::MKSNAPSHOT,
299
                           "Generated code cache for %d: %s\n",
300
1584
                           item.id.c_str(),
301
3168
                           size_str.c_str());
302
      }
303
#endif
304
    }
305
306
    // Global handles to the contexts can't be disposed before the
307
    // blob is created. So initialize all the contexts before adding them.
308
    // TODO(joyeecheung): figure out how to remove this restriction.
309
6
    creator.SetDefaultContext(default_context);
310
6
    size_t index = creator.AddContext(base_context);
311
6
    CHECK_EQ(index, SnapshotData::kNodeBaseContextIndex);
312
6
    index = creator.AddContext(main_context,
313
                               {SerializeNodeContextInternalFields, env});
314
6
    CHECK_EQ(index, SnapshotData::kNodeMainContextIndex);
315
  }
316
317
  // Must be out of HandleScope
318
  out->v8_snapshot_blob_data =
319
6
      creator.CreateBlob(SnapshotCreator::FunctionCodeHandling::kClear);
320
321
  // We must be able to rehash the blob when we restore it or otherwise
322
  // the hash seed would be fixed by V8, introducing a vulnerability.
323
6
  if (!out->v8_snapshot_blob_data.CanBeRehashed()) {
324
    return SNAPSHOT_ERROR;
325
  }
326
327
  // We cannot resurrect the handles from the snapshot, so make sure that
328
  // no handles are left open in the environment after the blob is created
329
  // (which should trigger a GC and close all handles that can be closed).
330
  bool queues_are_empty =
331

6
      env->req_wrap_queue()->IsEmpty() && env->handle_wrap_queue()->IsEmpty();
332

12
  if (!queues_are_empty ||
333
6
      per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
334
    PrintLibuvHandleInformation(env->event_loop(), stderr);
335
  }
336
6
  if (!queues_are_empty) {
337
    return SNAPSHOT_ERROR;
338
  }
339
6
  return 0;
340
}
341
342
6
int SnapshotBuilder::Generate(std::ostream& out,
343
                              const std::vector<std::string> args,
344
                              const std::vector<std::string> exec_args) {
345
12
  SnapshotData data;
346
6
  int exit_code = Generate(&data, args, exec_args);
347
6
  if (exit_code != 0) {
348
    return exit_code;
349
  }
350
6
  FormatBlob(out, &data);
351
6
  return exit_code;
352
}
353
354
26244
SnapshotableObject::SnapshotableObject(Environment* env,
355
                                       Local<Object> wrap,
356
26244
                                       EmbedderObjectType type)
357
26244
    : BaseObject(env, wrap), type_(type) {
358
26244
}
359
360
24
const char* SnapshotableObject::GetTypeNameChars() const {
361

24
  switch (type_) {
362
#define V(PropertyName, NativeTypeName)                                        \
363
  case EmbedderObjectType::k_##PropertyName: {                                 \
364
    return NativeTypeName::type_name.c_str();                                  \
365
  }
366
24
    SERIALIZABLE_OBJECT_TYPES(V)
367
#undef V
368
    default: { UNREACHABLE(); }
369
  }
370
}
371
372
24
bool IsSnapshotableType(FastStringKey key) {
373
#define V(PropertyName, NativeTypeName)                                        \
374
  if (key == NativeTypeName::type_name) {                                      \
375
    return true;                                                               \
376
  }
377


24
  SERIALIZABLE_OBJECT_TYPES(V)
378
#undef V
379
380
  return false;
381
}
382
383
21036
void DeserializeNodeInternalFields(Local<Object> holder,
384
                                   int index,
385
                                   StartupData payload,
386
                                   void* env) {
387
  per_process::Debug(DebugCategory::MKSNAPSHOT,
388
                     "Deserialize internal field %d of %p, size=%d\n",
389
42072
                     static_cast<int>(index),
390
21036
                     (*holder),
391
21036
                     static_cast<int>(payload.raw_size));
392
21036
  if (payload.raw_size == 0) {
393
    holder->SetAlignedPointerInInternalField(index, nullptr);
394
    return;
395
  }
396
397
21036
  Environment* env_ptr = static_cast<Environment*>(env);
398
21036
  const InternalFieldInfo* info =
399
      reinterpret_cast<const InternalFieldInfo*>(payload.data);
400
401

21036
  switch (info->type) {
402
#define V(PropertyName, NativeTypeName)                                        \
403
  case EmbedderObjectType::k_##PropertyName: {                                 \
404
    per_process::Debug(DebugCategory::MKSNAPSHOT,                              \
405
                       "Object %p is %s\n",                                    \
406
                       (*holder),                                              \
407
                       NativeTypeName::type_name.c_str());                     \
408
    env_ptr->EnqueueDeserializeRequest(                                        \
409
        NativeTypeName::Deserialize, holder, index, info->Copy());             \
410
    break;                                                                     \
411
  }
412
42072
    SERIALIZABLE_OBJECT_TYPES(V)
413
#undef V
414
    default: { UNREACHABLE(); }
415
  }
416
}
417
418
780
StartupData SerializeNodeContextInternalFields(Local<Object> holder,
419
                                               int index,
420
                                               void* env) {
421
  per_process::Debug(DebugCategory::MKSNAPSHOT,
422
                     "Serialize internal field, index=%d, holder=%p\n",
423
1560
                     static_cast<int>(index),
424
1560
                     *holder);
425
780
  void* ptr = holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
426
780
  if (ptr == nullptr) {
427
756
    return StartupData{nullptr, 0};
428
  }
429
430
  DCHECK(static_cast<BaseObject*>(ptr)->is_snapshotable());
431
24
  SnapshotableObject* obj = static_cast<SnapshotableObject*>(ptr);
432
  per_process::Debug(DebugCategory::MKSNAPSHOT,
433
                     "Object %p is %s, ",
434
24
                     *holder,
435
24
                     obj->GetTypeNameChars());
436
24
  InternalFieldInfo* info = obj->Serialize(index);
437
  per_process::Debug(DebugCategory::MKSNAPSHOT,
438
                     "payload size=%d\n",
439
24
                     static_cast<int>(info->length));
440
  return StartupData{reinterpret_cast<const char*>(info),
441
24
                     static_cast<int>(info->length)};
442
}
443
444
6
void SerializeBindingData(Environment* env,
445
                          SnapshotCreator* creator,
446
                          EnvSerializeInfo* info) {
447
6
  size_t i = 0;
448
6
  env->ForEachBindingData([&](FastStringKey key,
449
24
                              BaseObjectPtr<BaseObject> binding) {
450
    per_process::Debug(DebugCategory::MKSNAPSHOT,
451
                       "Serialize binding %i, %p, type=%s\n",
452
96
                       static_cast<int>(i),
453
48
                       *(binding->object()),
454
24
                       key.c_str());
455
456
24
    if (IsSnapshotableType(key)) {
457
24
      size_t index = creator->AddData(env->context(), binding->object());
458
      per_process::Debug(DebugCategory::MKSNAPSHOT,
459
                         "Serialized with index=%d\n",
460
24
                         static_cast<int>(index));
461
24
      info->bindings.push_back({key.c_str(), i, index});
462
24
      SnapshotableObject* ptr = static_cast<SnapshotableObject*>(binding.get());
463
24
      ptr->PrepareForSerialization(env->context(), creator);
464
    } else {
465
      UNREACHABLE();
466
    }
467
468
24
    i++;
469
24
  });
470
6
}
471
472
namespace mksnapshot {
473
474
void CompileSerializeMain(const FunctionCallbackInfo<Value>& args) {
475
  CHECK(args[0]->IsString());
476
  Local<String> filename = args[0].As<String>();
477
  Local<String> source = args[1].As<String>();
478
  Isolate* isolate = args.GetIsolate();
479
  Local<Context> context = isolate->GetCurrentContext();
480
  ScriptOrigin origin(isolate, filename, 0, 0, true);
481
  // TODO(joyeecheung): do we need all of these? Maybe we would want a less
482
  // internal version of them.
483
  std::vector<Local<String>> parameters = {
484
      FIXED_ONE_BYTE_STRING(isolate, "require"),
485
      FIXED_ONE_BYTE_STRING(isolate, "__filename"),
486
      FIXED_ONE_BYTE_STRING(isolate, "__dirname"),
487
  };
488
  ScriptCompiler::Source script_source(source, origin);
489
  Local<Function> fn;
490
  if (ScriptCompiler::CompileFunctionInContext(context,
491
                                               &script_source,
492
                                               parameters.size(),
493
                                               parameters.data(),
494
                                               0,
495
                                               nullptr,
496
                                               ScriptCompiler::kEagerCompile)
497
          .ToLocal(&fn)) {
498
    args.GetReturnValue().Set(fn);
499
  }
500
}
501
502
void SetSerializeCallback(const FunctionCallbackInfo<Value>& args) {
503
  Environment* env = Environment::GetCurrent(args);
504
  CHECK(env->snapshot_serialize_callback().IsEmpty());
505
  CHECK(args[0]->IsFunction());
506
  env->set_snapshot_serialize_callback(args[0].As<Function>());
507
}
508
509
void SetDeserializeCallback(const FunctionCallbackInfo<Value>& args) {
510
  Environment* env = Environment::GetCurrent(args);
511
  CHECK(env->snapshot_deserialize_callback().IsEmpty());
512
  CHECK(args[0]->IsFunction());
513
  env->set_snapshot_deserialize_callback(args[0].As<Function>());
514
}
515
516
void SetDeserializeMainFunction(const FunctionCallbackInfo<Value>& args) {
517
  Environment* env = Environment::GetCurrent(args);
518
  CHECK(env->snapshot_deserialize_main().IsEmpty());
519
  CHECK(args[0]->IsFunction());
520
  env->set_snapshot_deserialize_main(args[0].As<Function>());
521
}
522
523
1302
void Initialize(Local<Object> target,
524
                Local<Value> unused,
525
                Local<Context> context,
526
                void* priv) {
527
1302
  Environment* env = Environment::GetCurrent(context);
528
1302
  env->SetMethod(target, "compileSerializeMain", CompileSerializeMain);
529
1302
  env->SetMethod(target, "markBootstrapComplete", MarkBootstrapComplete);
530
1302
  env->SetMethod(target, "setSerializeCallback", SetSerializeCallback);
531
1302
  env->SetMethod(target, "setDeserializeCallback", SetDeserializeCallback);
532
1302
  env->SetMethod(
533
      target, "setDeserializeMainFunction", SetDeserializeMainFunction);
534
1302
}
535
536
5265
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
537
5265
  registry->Register(CompileSerializeMain);
538
5265
  registry->Register(MarkBootstrapComplete);
539
5265
  registry->Register(SetSerializeCallback);
540
5265
  registry->Register(SetDeserializeCallback);
541
5265
  registry->Register(SetDeserializeMainFunction);
542
5265
}
543
}  // namespace mksnapshot
544
}  // namespace node
545
546
5333
NODE_MODULE_CONTEXT_AWARE_INTERNAL(mksnapshot, node::mksnapshot::Initialize)
547
5265
NODE_MODULE_EXTERNAL_REFERENCE(mksnapshot,
548
                               node::mksnapshot::RegisterExternalReferences)