GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_snapshotable.cc Lines: 183 240 76.2 %
Date: 2022-07-30 04:16:22 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
5346
SnapshotData::~SnapshotData() {
42
5346
  if (data_ownership == DataOwnership::kOwned &&
43
6
      v8_snapshot_blob_data.data != nullptr) {
44
6
    delete[] v8_snapshot_blob_data.data;
45
  }
46
5346
}
47
48
template <typename T>
49
3192
void WriteVector(std::ostream* ss, const T* vec, size_t size) {
50
23868786
  for (size_t i = 0; i < size; i++) {
51
23865594
    *ss << std::to_string(vec[i]) << (i == size - 1 ? '\n' : ',');
52
  }
53
3192
}
54
55
3180
static std::string GetCodeCacheDefName(const std::string& id) {
56
3180
  char buf[64] = {0};
57
3180
  size_t size = id.size();
58
3180
  CHECK_LT(size, sizeof(buf));
59
69420
  for (size_t i = 0; i < size; ++i) {
60
66240
    char ch = id[i];
61

66240
    buf[i] = (ch == '-' || ch == '/') ? '_' : ch;
62
  }
63
3180
  return std::string(buf) + std::string("_cache_data");
64
}
65
66
1590
static std::string FormatSize(size_t size) {
67
1590
  char buf[64] = {0};
68
1590
  if (size < 1024) {
69
126
    snprintf(buf, sizeof(buf), "%.2fB", static_cast<double>(size));
70
1464
  } else if (size < 1024 * 1024) {
71
1464
    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
1590
  return buf;
77
}
78
79
1590
static void WriteStaticCodeCacheData(std::ostream* ss,
80
                                     const native_module::CodeCacheInfo& info) {
81
1590
  *ss << "static const uint8_t " << GetCodeCacheDefName(info.id) << "[] = {\n";
82
1590
  WriteVector(ss, info.data.data(), info.data.size());
83
1590
  *ss << "};";
84
1590
}
85
86
1590
static void WriteCodeCacheInitializer(std::ostream* ss, const std::string& id) {
87
3180
  std::string def_name = GetCodeCacheDefName(id);
88
1590
  *ss << "    { \"" << id << "\",\n";
89
1590
  *ss << "      {" << def_name << ",\n";
90
1590
  *ss << "       " << def_name << " + arraysize(" << def_name << "),\n";
91
1590
  *ss << "      }\n";
92
1590
  *ss << "    },\n";
93
1590
}
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
1596
  for (const auto& item : data->code_cache) {
118
1590
    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
6
)" << data->isolate_data_info
130
6
     << R"(
131
  // -- isolate_data_indices ends --
132
  ,
133
  // -- env_info begins --
134
6
)" << data->env_info
135
6
     << R"(
136
  // -- env_info ends --
137
  ,
138
  // -- code_cache begins --
139
  {)";
140
1596
  for (const auto& item : data->code_cache) {
141
1590
    WriteCodeCacheInitializer(&ss, item.id);
142
  }
143
6
  ss << R"(
144
  }
145
  // -- code_cache ends --
146
};
147
148
const SnapshotData* SnapshotBuilder::GetEmbeddedSnapshotData() {
149
  Mutex::ScopedLock lock(snapshot_data_mutex_);
150
  return &snapshot_data;
151
}
152
}  // namespace node
153
)";
154
6
}
155
156
Mutex SnapshotBuilder::snapshot_data_mutex_;
157
158
6546
const std::vector<intptr_t>& SnapshotBuilder::CollectExternalReferences() {
159

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

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

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

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


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

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