GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: node_snapshotable.cc Lines: 135 174 77.6 %
Date: 2022-05-17 04:15:46 Branches: 40 78 51.3 %

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_process.h"
15
#include "node_snapshot_builder.h"
16
#include "node_v8.h"
17
#include "node_v8_platform-inl.h"
18
19
#if HAVE_INSPECTOR
20
#include "inspector/worker_inspector.h"  // ParentInspectorHandle
21
#endif
22
23
namespace node {
24
25
using v8::Context;
26
using v8::Function;
27
using v8::FunctionCallbackInfo;
28
using v8::HandleScope;
29
using v8::Isolate;
30
using v8::Local;
31
using v8::MaybeLocal;
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
template <typename T>
42
24
void WriteVector(std::ostringstream* ss, const T* vec, size_t size) {
43
8472902
  for (size_t i = 0; i < size; i++) {
44
8472878
    *ss << std::to_string(vec[i]) << (i == size - 1 ? '\n' : ',');
45
  }
46
24
}
47
48
6
std::string FormatBlob(SnapshotData* data) {
49
12
  std::ostringstream ss;
50
51
6
  ss << R"(#include <cstddef>
52
#include "env.h"
53
#include "node_snapshot_builder.h"
54
#include "v8.h"
55
56
// This file is generated by tools/snapshot. Do not edit.
57
58
namespace node {
59
60
static const char blob_data[] = {
61
)";
62
6
  WriteVector(&ss, data->blob.data, data->blob.raw_size);
63
6
  ss << R"(};
64
65
static const int blob_size = )"
66
6
     << data->blob.raw_size << R"(;
67
68
SnapshotData snapshot_data {
69
  // -- blob begins --
70
  { blob_data, blob_size },
71
  // -- blob ends --
72
  // -- isolate_data_indices begins --
73
  {
74
)";
75
12
  WriteVector(&ss,
76
6
              data->isolate_data_indices.data(),
77
              data->isolate_data_indices.size());
78
6
  ss << R"(},
79
  // -- isolate_data_indices ends --
80
  // -- env_info begins --
81
6
)" << data->env_info
82
6
     << R"(
83
  // -- env_info ends --
84
};
85
86
const SnapshotData* SnapshotBuilder::GetEmbeddedSnapshotData() {
87
  Mutex::ScopedLock lock(snapshot_data_mutex_);
88
  return &snapshot_data;
89
}
90
}  // namespace node
91
)";
92
93
6
  return ss.str();
94
}
95
96
Mutex SnapshotBuilder::snapshot_data_mutex_;
97
98
5982
const std::vector<intptr_t>& SnapshotBuilder::CollectExternalReferences() {
99

5982
  static auto registry = std::make_unique<ExternalReferenceRegistry>();
100
5982
  return registry->external_references();
101
}
102
103
5976
void SnapshotBuilder::InitializeIsolateParams(const SnapshotData* data,
104
                                              Isolate::CreateParams* params) {
105
5976
  params->external_references = CollectExternalReferences().data();
106
5976
  params->snapshot_blob = const_cast<v8::StartupData*>(&(data->blob));
107
5976
}
108
109
6
void SnapshotBuilder::Generate(SnapshotData* out,
110
                               const std::vector<std::string> args,
111
                               const std::vector<std::string> exec_args) {
112
6
  Isolate* isolate = Isolate::Allocate();
113
6
  isolate->SetCaptureStackTraceForUncaughtExceptions(
114
      true, 10, v8::StackTrace::StackTraceOptions::kDetailed);
115
12
  per_process::v8_platform.Platform()->RegisterIsolate(isolate,
116
6
                                                       uv_default_loop());
117
6
  std::unique_ptr<NodeMainInstance> main_instance;
118
12
  std::string result;
119
120
  {
121
    const std::vector<intptr_t>& external_references =
122
6
        CollectExternalReferences();
123
12
    SnapshotCreator creator(isolate, external_references.data());
124
    Environment* env;
125
    {
126
      main_instance =
127
12
          NodeMainInstance::Create(isolate,
128
                                   uv_default_loop(),
129
6
                                   per_process::v8_platform.Platform(),
130
                                   args,
131
6
                                   exec_args);
132
      out->isolate_data_indices =
133
6
          main_instance->isolate_data()->Serialize(&creator);
134
135
12
      HandleScope scope(isolate);
136
137
      // The default context with only things created by V8.
138
6
      creator.SetDefaultContext(Context::New(isolate));
139
140
12
      auto CreateBaseContext = [&]() {
141
12
        TryCatch bootstrapCatch(isolate);
142
        // Run the per-context scripts.
143
12
        Local<Context> base_context = NewContext(isolate);
144
12
        if (bootstrapCatch.HasCaught()) {
145
          PrintCaughtException(isolate, base_context, bootstrapCatch);
146
          abort();
147
        }
148
12
        return base_context;
149
6
      };
150
151
      // The Node.js-specific context with primodials, can be used by workers
152
      // TODO(joyeecheung): investigate if this can be used by vm contexts
153
      // without breaking compatibility.
154
      {
155
6
        size_t index = creator.AddContext(CreateBaseContext());
156
6
        CHECK_EQ(index, SnapshotBuilder::kNodeBaseContextIndex);
157
      }
158
159
      // The main instance context.
160
      {
161
6
        Local<Context> main_context = CreateBaseContext();
162
6
        Context::Scope context_scope(main_context);
163
12
        TryCatch bootstrapCatch(isolate);
164
165
        // Create the environment.
166
6
        env = new Environment(main_instance->isolate_data(),
167
                              main_context,
168
                              args,
169
                              exec_args,
170
                              nullptr,
171
                              node::EnvironmentFlags::kDefaultFlags,
172
6
                              {});
173
174
        // Run scripts in lib/internal/bootstrap/
175
6
        MaybeLocal<Value> result = env->RunBootstrapping();
176
6
        if (bootstrapCatch.HasCaught()) {
177
          // TODO(joyeecheung): fail by exiting with a non-zero exit code.
178
          PrintCaughtException(isolate, main_context, bootstrapCatch);
179
          abort();
180
        }
181
        result.ToLocalChecked();
182
        // If --build-snapshot is true, lib/internal/main/mksnapshot.js would be
183
        // loaded via LoadEnvironment() to execute process.argv[1] as the entry
184
        // point (we currently only support this kind of entry point, but we
185
        // could also explore snapshotting other kinds of execution modes
186
        // in the future).
187
6
        if (per_process::cli_options->build_snapshot) {
188
#if HAVE_INSPECTOR
189
          env->InitializeInspector({});
190
#endif
191
          // TODO(joyeecheung): we could use the result for something special,
192
          // like setting up initializers that should be invoked at snapshot
193
          // dehydration.
194
          MaybeLocal<Value> result =
195
              LoadEnvironment(env, StartExecutionCallback{});
196
          if (bootstrapCatch.HasCaught()) {
197
            // TODO(joyeecheung): fail by exiting with a non-zero exit code.
198
            PrintCaughtException(isolate, main_context, bootstrapCatch);
199
            abort();
200
          }
201
          result.ToLocalChecked();
202
          // FIXME(joyeecheung): right now running the loop in the snapshot
203
          // builder seems to introduces inconsistencies in JS land that need to
204
          // be synchronized again after snapshot restoration.
205
          int exit_code = SpinEventLoop(env).FromMaybe(1);
206
          CHECK_EQ(exit_code, 0);
207
          if (bootstrapCatch.HasCaught()) {
208
            // TODO(joyeecheung): fail by exiting with a non-zero exit code.
209
            PrintCaughtException(isolate, main_context, bootstrapCatch);
210
            abort();
211
          }
212
        }
213
214
6
        if (per_process::enabled_debug_list.enabled(
215
                DebugCategory::MKSNAPSHOT)) {
216
          env->PrintAllBaseObjects();
217
          printf("Environment = %p\n", env);
218
        }
219
220
        // Serialize the native states
221
6
        out->env_info = env->Serialize(&creator);
222
        // Serialize the context
223
6
        size_t index = creator.AddContext(
224
6
            main_context, {SerializeNodeContextInternalFields, env});
225
6
        CHECK_EQ(index, SnapshotBuilder::kNodeMainContextIndex);
226
      }
227
    }
228
229
    // Must be out of HandleScope
230
    out->blob =
231
6
        creator.CreateBlob(SnapshotCreator::FunctionCodeHandling::kClear);
232
233
    // We must be able to rehash the blob when we restore it or otherwise
234
    // the hash seed would be fixed by V8, introducing a vulnerability.
235
6
    CHECK(out->blob.CanBeRehashed());
236
237
    // We cannot resurrect the handles from the snapshot, so make sure that
238
    // no handles are left open in the environment after the blob is created
239
    // (which should trigger a GC and close all handles that can be closed).
240
6
    if (!env->req_wrap_queue()->IsEmpty()
241
6
        || !env->handle_wrap_queue()->IsEmpty()
242

12
        || per_process::enabled_debug_list.enabled(DebugCategory::MKSNAPSHOT)) {
243
      PrintLibuvHandleInformation(env->event_loop(), stderr);
244
    }
245
6
    CHECK(env->req_wrap_queue()->IsEmpty());
246
6
    CHECK(env->handle_wrap_queue()->IsEmpty());
247
248
    // Must be done while the snapshot creator isolate is entered i.e. the
249
    // creator is still alive.
250
6
    FreeEnvironment(env);
251
6
    main_instance->Dispose();
252
  }
253
254
6
  per_process::v8_platform.Platform()->UnregisterIsolate(isolate);
255
6
}
256
257
6
std::string SnapshotBuilder::Generate(
258
    const std::vector<std::string> args,
259
    const std::vector<std::string> exec_args) {
260
12
  SnapshotData data;
261
6
  Generate(&data, args, exec_args);
262
6
  std::string result = FormatBlob(&data);
263
6
  delete[] data.blob.data;
264
6
  return result;
265
}
266
267
24088
SnapshotableObject::SnapshotableObject(Environment* env,
268
                                       Local<Object> wrap,
269
24088
                                       EmbedderObjectType type)
270
24088
    : BaseObject(env, wrap), type_(type) {
271
24088
}
272
273
24
const char* SnapshotableObject::GetTypeNameChars() const {
274

24
  switch (type_) {
275
#define V(PropertyName, NativeTypeName)                                        \
276
  case EmbedderObjectType::k_##PropertyName: {                                 \
277
    return NativeTypeName::type_name.c_str();                                  \
278
  }
279
24
    SERIALIZABLE_OBJECT_TYPES(V)
280
#undef V
281
    default: { UNREACHABLE(); }
282
  }
283
}
284
285
24
bool IsSnapshotableType(FastStringKey key) {
286
#define V(PropertyName, NativeTypeName)                                        \
287
  if (key == NativeTypeName::type_name) {                                      \
288
    return true;                                                               \
289
  }
290


24
  SERIALIZABLE_OBJECT_TYPES(V)
291
#undef V
292
293
  return false;
294
}
295
296
20688
void DeserializeNodeInternalFields(Local<Object> holder,
297
                                   int index,
298
                                   StartupData payload,
299
                                   void* env) {
300
  per_process::Debug(DebugCategory::MKSNAPSHOT,
301
                     "Deserialize internal field %d of %p, size=%d\n",
302
41376
                     static_cast<int>(index),
303
20688
                     (*holder),
304
20688
                     static_cast<int>(payload.raw_size));
305
20688
  if (payload.raw_size == 0) {
306
    holder->SetAlignedPointerInInternalField(index, nullptr);
307
    return;
308
  }
309
310
20688
  Environment* env_ptr = static_cast<Environment*>(env);
311
20688
  const InternalFieldInfo* info =
312
      reinterpret_cast<const InternalFieldInfo*>(payload.data);
313
314

20688
  switch (info->type) {
315
#define V(PropertyName, NativeTypeName)                                        \
316
  case EmbedderObjectType::k_##PropertyName: {                                 \
317
    per_process::Debug(DebugCategory::MKSNAPSHOT,                              \
318
                       "Object %p is %s\n",                                    \
319
                       (*holder),                                              \
320
                       NativeTypeName::type_name.c_str());                     \
321
    env_ptr->EnqueueDeserializeRequest(                                        \
322
        NativeTypeName::Deserialize, holder, index, info->Copy());             \
323
    break;                                                                     \
324
  }
325
41376
    SERIALIZABLE_OBJECT_TYPES(V)
326
#undef V
327
    default: { UNREACHABLE(); }
328
  }
329
}
330
331
780
StartupData SerializeNodeContextInternalFields(Local<Object> holder,
332
                                               int index,
333
                                               void* env) {
334
  per_process::Debug(DebugCategory::MKSNAPSHOT,
335
                     "Serialize internal field, index=%d, holder=%p\n",
336
1560
                     static_cast<int>(index),
337
1560
                     *holder);
338
780
  void* ptr = holder->GetAlignedPointerFromInternalField(BaseObject::kSlot);
339
780
  if (ptr == nullptr) {
340
756
    return StartupData{nullptr, 0};
341
  }
342
343
  DCHECK(static_cast<BaseObject*>(ptr)->is_snapshotable());
344
24
  SnapshotableObject* obj = static_cast<SnapshotableObject*>(ptr);
345
  per_process::Debug(DebugCategory::MKSNAPSHOT,
346
                     "Object %p is %s, ",
347
24
                     *holder,
348
24
                     obj->GetTypeNameChars());
349
24
  InternalFieldInfo* info = obj->Serialize(index);
350
  per_process::Debug(DebugCategory::MKSNAPSHOT,
351
                     "payload size=%d\n",
352
24
                     static_cast<int>(info->length));
353
  return StartupData{reinterpret_cast<const char*>(info),
354
24
                     static_cast<int>(info->length)};
355
}
356
357
6
void SerializeBindingData(Environment* env,
358
                          SnapshotCreator* creator,
359
                          EnvSerializeInfo* info) {
360
6
  size_t i = 0;
361
6
  env->ForEachBindingData([&](FastStringKey key,
362
24
                              BaseObjectPtr<BaseObject> binding) {
363
    per_process::Debug(DebugCategory::MKSNAPSHOT,
364
                       "Serialize binding %i, %p, type=%s\n",
365
96
                       static_cast<int>(i),
366
48
                       *(binding->object()),
367
24
                       key.c_str());
368
369
24
    if (IsSnapshotableType(key)) {
370
24
      size_t index = creator->AddData(env->context(), binding->object());
371
      per_process::Debug(DebugCategory::MKSNAPSHOT,
372
                         "Serialized with index=%d\n",
373
24
                         static_cast<int>(index));
374
24
      info->bindings.push_back({key.c_str(), i, index});
375
24
      SnapshotableObject* ptr = static_cast<SnapshotableObject*>(binding.get());
376
24
      ptr->PrepareForSerialization(env->context(), creator);
377
    } else {
378
      UNREACHABLE();
379
    }
380
381
24
    i++;
382
24
  });
383
6
}
384
385
namespace mksnapshot {
386
387
static void CompileSnapshotMain(const FunctionCallbackInfo<Value>& args) {
388
  CHECK(args[0]->IsString());
389
  Local<String> filename = args[0].As<String>();
390
  Local<String> source = args[1].As<String>();
391
  Isolate* isolate = args.GetIsolate();
392
  Local<Context> context = isolate->GetCurrentContext();
393
  ScriptOrigin origin(isolate, filename, 0, 0, true);
394
  // TODO(joyeecheung): do we need all of these? Maybe we would want a less
395
  // internal version of them.
396
  std::vector<Local<String>> parameters = {
397
      FIXED_ONE_BYTE_STRING(isolate, "require"),
398
      FIXED_ONE_BYTE_STRING(isolate, "__filename"),
399
      FIXED_ONE_BYTE_STRING(isolate, "__dirname"),
400
  };
401
  ScriptCompiler::Source script_source(source, origin);
402
  Local<Function> fn;
403
  if (ScriptCompiler::CompileFunctionInContext(context,
404
                                               &script_source,
405
                                               parameters.size(),
406
                                               parameters.data(),
407
                                               0,
408
                                               nullptr,
409
                                               ScriptCompiler::kEagerCompile)
410
          .ToLocal(&fn)) {
411
    args.GetReturnValue().Set(fn);
412
  }
413
}
414
415
2831
static void Initialize(Local<Object> target,
416
                       Local<Value> unused,
417
                       Local<Context> context,
418
                       void* priv) {
419
2831
  Environment* env = Environment::GetCurrent(context);
420
2831
  Isolate* isolate = context->GetIsolate();
421
2831
  env->SetMethod(target, "compileSnapshotMain", CompileSnapshotMain);
422
  target
423
2831
      ->Set(context,
424
            FIXED_ONE_BYTE_STRING(isolate, "cleanups"),
425
8493
            v8::Array::New(isolate))
426
      .Check();
427
2831
}
428
429
5178
static void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
430
5178
  registry->Register(CompileSnapshotMain);
431
5178
  registry->Register(MarkBootstrapComplete);
432
5178
}
433
}  // namespace mksnapshot
434
}  // namespace node
435
436
5246
NODE_MODULE_CONTEXT_AWARE_INTERNAL(mksnapshot, node::mksnapshot::Initialize)
437
5178
NODE_MODULE_EXTERNAL_REFERENCE(mksnapshot,
438
                               node::mksnapshot::RegisterExternalReferences)