@ 261105.16
current
#ifdef NDEBUG
#undef NDEBUG
#endif
#include <assert.h>
#include "cppgc/platform.h"
#include "executable_wrapper.h"
#include "node.h"

#include <algorithm>

// Note: This file is being referred to from doc/api/embedding.md, and excerpts
// from it are included in the documentation. Try to keep these in sync.
// Snapshot support is not part of the embedder API docs yet due to its
// experimental nature, although it is of course documented in node.h.

using node::CommonEnvironmentSetup;
using node::Environment;
using node::MultiIsolatePlatform;
using v8::Context;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Locker;
using v8::MaybeLocal;
using v8::V8;
using v8::Value;

static int RunNodeInstance(MultiIsolatePlatform* platform,
                           const std::vector<std::string>& args,
                           const std::vector<std::string>& exec_args);
static int RunSnapshotWithIsolateSettings(
    MultiIsolatePlatform* platform,
    const node::EmbedderSnapshotData* snapshot,
    const std::vector<std::string>& args,
    const std::vector<std::string>& exec_args);

// --create-v8-startup-blob <file>: a plain V8 startup blob (what V8's
// mksnapshot produces), to test building the Node.js snapshot on top of one.
static int CreateV8StartupBlob(MultiIsolatePlatform* platform,
                               const std::string& path) {
  std::unique_ptr<v8::ArrayBuffer::Allocator> allocator(
      v8::ArrayBuffer::Allocator::NewDefaultAllocator());
  v8::Isolate::CreateParams params;
  params.array_buffer_allocator = allocator.get();
  uv_loop_t loop;
  assert(uv_loop_init(&loop) == 0);
  v8::Isolate* isolate = v8::Isolate::Allocate();
  platform->RegisterIsolate(isolate, &loop);
  v8::StartupData blob;
  {
    v8::SnapshotCreator creator(isolate, params);
    {
      v8::HandleScope handle_scope(isolate);
      creator.SetDefaultContext(v8::Context::New(isolate));
    }
    blob =
        creator.CreateBlob(v8::SnapshotCreator::FunctionCodeHandling::kClear);
  }
  bool platform_finished = false;
  platform->AddIsolateFinishedCallback(
      isolate,
      [](void* data) {
        bool* finished = static_cast<bool*>(data);
        *finished = true;
      },
      &platform_finished);
  platform->DisposeIsolate(isolate);
  while (!platform_finished) uv_run(&loop, UV_RUN_ONCE);
  uv_loop_close(&loop);
  assert(blob.data != nullptr);
  FILE* fp = fopen(path.c_str(), "wb");
  assert(fp != nullptr);
  size_t written = fwrite(blob.data, blob.raw_size, 1, fp);
  assert(written == 1);
  fclose(fp);
  delete[] blob.data;
  return 0;
}

static std::vector<char> base_blob_bytes;
static v8::StartupData base_blob{nullptr, 0};

NODE_MAIN(int argc, node::argv_type raw_argv[]) {
  char** argv = nullptr;
  node::FixupMain(argc, raw_argv, &argv);

  std::vector<std::string> args(argv, argv + argc);
  uint32_t flags =
      node::ProcessInitializationFlags::kNoInitializeV8 |
      node::ProcessInitializationFlags::kNoInitializeNodeV8Platform |
      // This is used to test NODE_REPL_EXTERNAL_MODULE is disabled with
      // kDisableNodeOptionsEnv. If other tests need NODE_OPTIONS
      // support in the future, split this configuration out as a
      // command line option.
      node::ProcessInitializationFlags::kDisableNodeOptionsEnv |
      node::ProcessInitializationFlags::kNoInitializeCppgc;
  auto it =
      std::find(args.begin(), args.end(), "--no-harvest-builtin-code-cache");
  if (it != args.end()) {
    args.erase(it);
    flags |= node::ProcessInitializationFlags::kNoHarvestBuiltinCodeCache;
  }
  // --embedder-scrub-env=<names>: remove the environment variables other than
  // the comma-separated <names> before initializing Node.js.
  static constexpr std::string_view kScrubEnvFlag = "--embedder-scrub-env=";
  it = std::find_if(args.begin(), args.end(), [](const std::string& arg) {
    return arg.starts_with(kScrubEnvFlag);
  });
  if (it != args.end()) {
    node::ProcessEnvironmentScrubOptions scrub_options;
    const std::string names = it->substr(kScrubEnvFlag.size());
    for (size_t begin = 0; begin < names.size();) {
      size_t end = names.find(',', begin);
      if (end == std::string::npos) end = names.size();
      scrub_options.allow.push_back(names.substr(begin, end - begin));
      begin = end + 1;
    }
    args.erase(it);
    if (node::ScrubProcessEnvironment(scrub_options).IsNothing()) {
      fprintf(
          stderr, "%s: ScrubProcessEnvironment() failed\n", args[0].c_str());
      return 1;
    }
  }
  std::shared_ptr<node::InitializationResult> result =
      node::InitializeOncePerProcess(
          args, static_cast<node::ProcessInitializationFlags::Flags>(flags));

  for (const std::string& error : result->errors())
    fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str());
  if (result->early_return() != 0) {
    return result->exit_code();
  }

  std::unique_ptr<MultiIsolatePlatform> platform =
      MultiIsolatePlatform::Create(4);
  V8::InitializePlatform(platform.get());
  cppgc::InitializeProcess(platform->GetPageAllocator());
  V8::Initialize();

  // --embedder-run-twice: two sequential instances in one process, each
  // loading (and freeing) its own copy of the snapshot.
  std::vector<std::string> instance_args = result->args();
  auto twice = std::find(
      instance_args.begin(), instance_args.end(), "--embedder-run-twice");
  int runs = 1;
  if (twice != instance_args.end()) {
    instance_args.erase(twice);
    runs = 2;
  }
  int ret = 0;
  for (int i = 0; i < runs && ret == 0; i++) {
    ret = RunNodeInstance(platform.get(), instance_args, result->exec_args());
  }

  V8::Dispose();
  V8::DisposePlatform();

  node::TearDownOncePerProcess();
  return ret;
}

int RunNodeInstance(MultiIsolatePlatform* platform,
                    const std::vector<std::string>& args,
                    const std::vector<std::string>& exec_args) {
  int exit_code = 0;

  // Format of the arguments of this binary:
  // Building snapshot:
  // embedtest js_code_to_eval arg1 arg2...
  //           --embedder-snapshot-blob blob-path
  //           --embedder-snapshot-create
  //           [--embedder-snapshot-as-file]
  //           [--without-code-cache]
  // Running snapshot:
  // embedtest --embedder-snapshot-blob blob-path
  //           [--embedder-snapshot-as-file]
  //           [--embedder-isolate-settings]
  //           arg1 arg2...
  // No snapshot:
  // embedtest arg1 arg2...
  node::EmbedderSnapshotData::Pointer snapshot;

  std::string binary_path = args[0];
  std::vector<std::string> filtered_args;
  bool is_building_snapshot = false;
  bool snapshot_as_file = false;
  bool with_isolate_settings = false;
  std::optional<node::SnapshotConfig> snapshot_config;
  std::string snapshot_blob_path;
  for (size_t i = 0; i < args.size(); ++i) {
    const std::string& arg = args[i];
    if (arg == "--embedder-snapshot-create") {
      is_building_snapshot = true;
    } else if (arg == "--embedder-snapshot-as-file") {
      snapshot_as_file = true;
    } else if (arg == "--embedder-isolate-settings") {
      with_isolate_settings = true;
    } else if (arg == "--without-code-cache") {
      if (!snapshot_config.has_value()) {
        snapshot_config = node::SnapshotConfig{};
      }
      snapshot_config.value().flags = static_cast<node::SnapshotFlags>(
          static_cast<uint32_t>(snapshot_config.value().flags) |
          static_cast<uint32_t>(node::SnapshotFlags::kWithoutCodeCache));
    } else if (arg == "--embedder-snapshot-blob") {
      assert(i + 1 < args.size());
      snapshot_blob_path = args[i + 1];
      i++;
    } else if (arg == "--create-v8-startup-blob") {
      assert(i + 1 < args.size());
      return CreateV8StartupBlob(platform, args[i + 1]);
    } else if (arg == "--embedder-snapshot-base-blob") {
      assert(i + 1 < args.size());
      FILE* fp = fopen(args[i + 1].c_str(), "rb");
      assert(fp != nullptr);
      fseek(fp, 0, SEEK_END);
      base_blob_bytes.resize(ftell(fp));
      fseek(fp, 0, SEEK_SET);
      size_t read =
          fread(base_blob_bytes.data(), base_blob_bytes.size(), 1, fp);
      assert(read == 1);
      fclose(fp);
      base_blob = {base_blob_bytes.data(),
                   static_cast<int>(base_blob_bytes.size())};
      if (!snapshot_config.has_value()) {
        snapshot_config = node::SnapshotConfig{};
      }
      snapshot_config.value().base_blob = &base_blob;
      i++;
    } else {
      filtered_args.push_back(arg);
    }
  }

  if (!snapshot_blob_path.empty() && !is_building_snapshot) {
    FILE* fp = fopen(snapshot_blob_path.c_str(), "rb");
    assert(fp != nullptr);
    if (snapshot_as_file) {
      snapshot = node::EmbedderSnapshotData::FromFile(fp);
    } else {
      uv_fs_t req = uv_fs_t();
      int statret =
          uv_fs_stat(nullptr, &req, snapshot_blob_path.c_str(), nullptr);
      assert(statret == 0);
      size_t filesize = req.statbuf.st_size;
      uv_fs_req_cleanup(&req);

      std::vector<char> vec(filesize);
      size_t read = fread(vec.data(), filesize, 1, fp);
      assert(read == 1);
      snapshot = node::EmbedderSnapshotData::FromBlob(vec);
    }
    assert(snapshot);
    int ret = fclose(fp);
    assert(ret == 0);
  }

  if (is_building_snapshot) {
    // It contains at least the binary path, the code to snapshot,
    // and --embedder-snapshot-create (which is filtered, so at least
    // 2 arguments should remain after filtering).
    assert(filtered_args.size() >= 2);
    // Insert an anonymous filename as process.argv[1].
    filtered_args.insert(filtered_args.begin() + 1,
                         node::GetAnonymousMainPath());
  }

  if (snapshot && with_isolate_settings) {
    return RunSnapshotWithIsolateSettings(
        platform, snapshot.get(), filtered_args, exec_args);
  }

  std::vector<std::string> errors;
  std::unique_ptr<CommonEnvironmentSetup> setup;

  if (snapshot) {
    setup = CommonEnvironmentSetup::CreateFromSnapshot(
        platform, &errors, snapshot.get(), filtered_args, exec_args);
  } else if (is_building_snapshot) {
    if (snapshot_config.has_value()) {
      setup = CommonEnvironmentSetup::CreateForSnapshotting(
          platform, &errors, filtered_args, exec_args, snapshot_config.value());
    } else {
      setup = CommonEnvironmentSetup::CreateForSnapshotting(
          platform, &errors, filtered_args, exec_args);
    }
  } else {
    setup = CommonEnvironmentSetup::Create(
        platform, &errors, filtered_args, exec_args);
  }
  if (!setup) {
    for (const std::string& err : errors)
      fprintf(stderr, "%s: %s\n", binary_path.c_str(), err.c_str());
    return 1;
  }

  Isolate* isolate = setup->isolate();
  Environment* env = setup->env();

  {
    Locker locker(isolate);
    Isolate::Scope isolate_scope(isolate);
    HandleScope handle_scope(isolate);
    Context::Scope context_scope(setup->context());

    MaybeLocal<Value> loadenv_ret;
    if (snapshot) {  // Deserializing snapshot
      loadenv_ret = node::LoadEnvironment(env, node::StartExecutionCallback{});
    } else if (is_building_snapshot) {
      // Environment created for snapshotting must set process.argv[1] to
      // the name of the main script, which was inserted above.
      loadenv_ret = node::LoadEnvironment(
          env,
          "const assert = require('assert');"
          "assert(require('v8').startupSnapshot.isBuildingSnapshot());"
          "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };"
          "globalThis.require = require;"
          "require('vm').runInThisContext(process.argv[2]);");
    } else {
      loadenv_ret = node::LoadEnvironment(
          env,
          "const publicRequire = require('module').createRequire(process.cwd() "
          "+ '/');"
          "globalThis.require = publicRequire;"
          "globalThis.embedVars = { nön_ascıı: '🏳️‍🌈' };"
          "require('vm').runInThisContext(process.argv[1]);");
    }

    if (loadenv_ret.IsEmpty())  // There has been a JS exception.
      return 1;

    exit_code = node::SpinEventLoop(env).FromMaybe(1);

    if (!snapshot_blob_path.empty() && is_building_snapshot) {
      snapshot = setup->CreateSnapshot();
      assert(snapshot);

      FILE* fp = fopen(snapshot_blob_path.c_str(), "wb");
      assert(fp != nullptr);
      if (snapshot_as_file) {
        snapshot->ToFile(fp);
      } else {
        const std::vector<char> vec = snapshot->ToBlob();
        size_t written = fwrite(vec.data(), vec.size(), 1, fp);
        assert(written == 1);
      }
      int ret = fclose(fp);
      assert(ret == 0);
    }
  }

  node::Stop(env);

  return exit_code;
}

// CommonEnvironmentSetup does not take IsolateSettings, so this goes through
// NewIsolate()/CreateIsolateData()/CreateEnvironment() directly.
static int RunSnapshotWithIsolateSettings(
    MultiIsolatePlatform* platform,
    const node::EmbedderSnapshotData* snapshot,
    const std::vector<std::string>& args,
    const std::vector<std::string>& exec_args) {
  uv_loop_t loop;
  int ret = uv_loop_init(&loop);
  assert(ret == 0);

  std::shared_ptr<node::ArrayBufferAllocator> allocator =
      node::ArrayBufferAllocator::Create();
  node::IsolateSettings settings;
  settings.prepare_stack_trace_callback = [](Local<Context> context,
                                             Local<Value> exception,
                                             Local<v8::Array> trace) {
    return MaybeLocal<Value>(v8::String::NewFromUtf8Literal(
        v8::Isolate::GetCurrent(), "stack trace prepared by the embedder"));
  };
  Isolate* isolate =
      node::NewIsolate(allocator, &loop, platform, snapshot, settings);
  assert(isolate != nullptr);

  int exit_code = 1;
  {
    Locker locker(isolate);
    Isolate::Scope isolate_scope(isolate);
    HandleScope handle_scope(isolate);

    std::unique_ptr<node::IsolateData, decltype(&node::FreeIsolateData)>
        isolate_data(node::CreateIsolateData(
                         isolate, &loop, platform, allocator.get(), snapshot),
                     node::FreeIsolateData);
    std::unique_ptr<Environment, decltype(&node::FreeEnvironment)> env(
        node::CreateEnvironment(
            isolate_data.get(), Local<Context>(), args, exec_args),
        node::FreeEnvironment);
    assert(env);

    Context::Scope context_scope(node::GetMainContext(env.get()));
    if (!node::LoadEnvironment(env.get(), node::StartExecutionCallback{})
             .IsEmpty()) {
      exit_code = node::SpinEventLoop(env.get()).FromMaybe(1);
    }
    node::Stop(env.get());
  }

  bool platform_finished = false;
  platform->AddIsolateFinishedCallback(
      isolate,
      [](void* data) {
        bool* finished = static_cast<bool*>(data);
        *finished = true;
      },
      &platform_finished);
  platform->DisposeIsolate(isolate);
  while (!platform_finished) uv_run(&loop, UV_RUN_ONCE);
  ret = uv_loop_close(&loop);
  assert(ret == 0);

  return exit_code;
}