GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_snapshotable.cc Lines: 177 218 81.2 %
Date: 2022-06-12 04:16:28 Branches: 57 98 58.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_env.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::MaybeLocal;
33
using v8::Object;
34
using v8::ScriptCompiler;
35
using v8::ScriptOrigin;
36
using v8::SnapshotCreator;
37
using v8::StartupData;
38
using v8::String;
39
using v8::TryCatch;
40
using v8::Value;
41
42
template <typename T>
43
3192
void WriteVector(std::ostringstream* ss, const T* vec, size_t size) {
44
23612292
  for (size_t i = 0; i < size; i++) {
45
23609100
    *ss << std::to_string(vec[i]) << (i == size - 1 ? '\n' : ',');
46
  }
47
3192
}
48
49
3168
static std::string GetCodeCacheDefName(const std::string& id) {
50
3168
  char buf[64] = {0};
51
3168
  size_t size = id.size();
52
3168
  CHECK_LT(size, sizeof(buf));
53
68832
  for (size_t i = 0; i < size; ++i) {
54
65664
    char ch = id[i];
55

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

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

12
        || per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
316
      PrintLibuvHandleInformation(env->event_loop(), stderr);
317
    }
318
6
    CHECK(env->req_wrap_queue()->IsEmpty());
319
6
    CHECK(env->handle_wrap_queue()->IsEmpty());
320
321
    // Must be done while the snapshot creator isolate is entered i.e. the
322
    // creator is still alive.
323
6
    FreeEnvironment(env);
324
6
    main_instance->Dispose();
325
  }
326
327
6
  per_process::v8_platform.Platform()->UnregisterIsolate(isolate);
328
6
}
329
330
6
std::string SnapshotBuilder::Generate(
331
    const std::vector<std::string> args,
332
    const std::vector<std::string> exec_args) {
333
12
  SnapshotData data;
334
6
  Generate(&data, args, exec_args);
335
6
  std::string result = FormatBlob(&data);
336
6
  delete[] data.v8_snapshot_blob_data.data;
337
6
  return result;
338
}
339
340
24212
SnapshotableObject::SnapshotableObject(Environment* env,
341
                                       Local<Object> wrap,
342
24212
                                       EmbedderObjectType type)
343
24212
    : BaseObject(env, wrap), type_(type) {
344
24212
}
345
346
24
const char* SnapshotableObject::GetTypeNameChars() const {
347

24
  switch (type_) {
348
#define V(PropertyName, NativeTypeName)                                        \
349
  case EmbedderObjectType::k_##PropertyName: {                                 \
350
    return NativeTypeName::type_name.c_str();                                  \
351
  }
352
24
    SERIALIZABLE_OBJECT_TYPES(V)
353
#undef V
354
    default: { UNREACHABLE(); }
355
  }
356
}
357
358
24
bool IsSnapshotableType(FastStringKey key) {
359
#define V(PropertyName, NativeTypeName)                                        \
360
  if (key == NativeTypeName::type_name) {                                      \
361
    return true;                                                               \
362
  }
363


24
  SERIALIZABLE_OBJECT_TYPES(V)
364
#undef V
365
366
  return false;
367
}
368
369
20796
void DeserializeNodeInternalFields(Local<Object> holder,
370
                                   int index,
371
                                   StartupData payload,
372
                                   void* env) {
373
  per_process::Debug(DebugCategory::MKSNAPSHOT,
374
                     "Deserialize internal field %d of %p, size=%d\n",
375
41592
                     static_cast<int>(index),
376
20796
                     (*holder),
377
20796
                     static_cast<int>(payload.raw_size));
378
20796
  if (payload.raw_size == 0) {
379
    holder->SetAlignedPointerInInternalField(index, nullptr);
380
    return;
381
  }
382
383
20796
  Environment* env_ptr = static_cast<Environment*>(env);
384
20796
  const InternalFieldInfo* info =
385
      reinterpret_cast<const InternalFieldInfo*>(payload.data);
386
387

20796
  switch (info->type) {
388
#define V(PropertyName, NativeTypeName)                                        \
389
  case EmbedderObjectType::k_##PropertyName: {                                 \
390
    per_process::Debug(DebugCategory::MKSNAPSHOT,                              \
391
                       "Object %p is %s\n",                                    \
392
                       (*holder),                                              \
393
                       NativeTypeName::type_name.c_str());                     \
394
    env_ptr->EnqueueDeserializeRequest(                                        \
395
        NativeTypeName::Deserialize, holder, index, info->Copy());             \
396
    break;                                                                     \
397
  }
398
41592
    SERIALIZABLE_OBJECT_TYPES(V)
399
#undef V
400
    default: { UNREACHABLE(); }
401
  }
402
}
403
404
780
StartupData SerializeNodeContextInternalFields(Local<Object> holder,
405
                                               int index,
406
                                               void* env) {
407
  per_process::Debug(DebugCategory::MKSNAPSHOT,
408
                     "Serialize internal field, index=%d, holder=%p\n",
409
1560
                     static_cast<int>(index),
410
1560
                     *holder);
411
780
  void* ptr = holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
412
780
  if (ptr == nullptr) {
413
756
    return StartupData{nullptr, 0};
414
  }
415
416
  DCHECK(static_cast<BaseObject*>(ptr)->is_snapshotable());
417
24
  SnapshotableObject* obj = static_cast<SnapshotableObject*>(ptr);
418
  per_process::Debug(DebugCategory::MKSNAPSHOT,
419
                     "Object %p is %s, ",
420
24
                     *holder,
421
24
                     obj->GetTypeNameChars());
422
24
  InternalFieldInfo* info = obj->Serialize(index);
423
  per_process::Debug(DebugCategory::MKSNAPSHOT,
424
                     "payload size=%d\n",
425
24
                     static_cast<int>(info->length));
426
  return StartupData{reinterpret_cast<const char*>(info),
427
24
                     static_cast<int>(info->length)};
428
}
429
430
6
void SerializeBindingData(Environment* env,
431
                          SnapshotCreator* creator,
432
                          EnvSerializeInfo* info) {
433
6
  size_t i = 0;
434
6
  env->ForEachBindingData([&](FastStringKey key,
435
24
                              BaseObjectPtr<BaseObject> binding) {
436
    per_process::Debug(DebugCategory::MKSNAPSHOT,
437
                       "Serialize binding %i, %p, type=%s\n",
438
96
                       static_cast<int>(i),
439
48
                       *(binding->object()),
440
24
                       key.c_str());
441
442
24
    if (IsSnapshotableType(key)) {
443
24
      size_t index = creator->AddData(env->context(), binding->object());
444
      per_process::Debug(DebugCategory::MKSNAPSHOT,
445
                         "Serialized with index=%d\n",
446
24
                         static_cast<int>(index));
447
24
      info->bindings.push_back({key.c_str(), i, index});
448
24
      SnapshotableObject* ptr = static_cast<SnapshotableObject*>(binding.get());
449
24
      ptr->PrepareForSerialization(env->context(), creator);
450
    } else {
451
      UNREACHABLE();
452
    }
453
454
24
    i++;
455
24
  });
456
6
}
457
458
namespace mksnapshot {
459
460
static void CompileSnapshotMain(const FunctionCallbackInfo<Value>& args) {
461
  CHECK(args[0]->IsString());
462
  Local<String> filename = args[0].As<String>();
463
  Local<String> source = args[1].As<String>();
464
  Isolate* isolate = args.GetIsolate();
465
  Local<Context> context = isolate->GetCurrentContext();
466
  ScriptOrigin origin(isolate, filename, 0, 0, true);
467
  // TODO(joyeecheung): do we need all of these? Maybe we would want a less
468
  // internal version of them.
469
  std::vector<Local<String>> parameters = {
470
      FIXED_ONE_BYTE_STRING(isolate, "require"),
471
      FIXED_ONE_BYTE_STRING(isolate, "__filename"),
472
      FIXED_ONE_BYTE_STRING(isolate, "__dirname"),
473
  };
474
  ScriptCompiler::Source script_source(source, origin);
475
  Local<Function> fn;
476
  if (ScriptCompiler::CompileFunctionInContext(context,
477
                                               &script_source,
478
                                               parameters.size(),
479
                                               parameters.data(),
480
                                               0,
481
                                               nullptr,
482
                                               ScriptCompiler::kEagerCompile)
483
          .ToLocal(&fn)) {
484
    args.GetReturnValue().Set(fn);
485
  }
486
}
487
488
2858
static void Initialize(Local<Object> target,
489
                       Local<Value> unused,
490
                       Local<Context> context,
491
                       void* priv) {
492
2858
  Environment* env = Environment::GetCurrent(context);
493
2858
  Isolate* isolate = context->GetIsolate();
494
2858
  env->SetMethod(target, "compileSnapshotMain", CompileSnapshotMain);
495
  target
496
2858
      ->Set(context,
497
            FIXED_ONE_BYTE_STRING(isolate, "cleanups"),
498
8574
            v8::Array::New(isolate))
499
      .Check();
500
2858
}
501
502
5205
static void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
503
5205
  registry->Register(CompileSnapshotMain);
504
5205
  registry->Register(MarkBootstrapComplete);
505
5205
}
506
}  // namespace mksnapshot
507
}  // namespace node
508
509
5273
NODE_MODULE_CONTEXT_AWARE_INTERNAL(mksnapshot, node::mksnapshot::Initialize)
510
5205
NODE_MODULE_EXTERNAL_REFERENCE(mksnapshot,
511
                               node::mksnapshot::RegisterExternalReferences)