Coverage Report

Created: 2025-10-31 09:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/node/test/fuzzers/fuzz_common.cc
Line
Count
Source
1
// Copyright 2025 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include "fuzz_common.h"
16
17
#include <cstdlib>
18
#include <string>
19
#include <vector>
20
21
#include "uv.h"
22
#include "v8.h"
23
24
#include "node.h"
25
#include "node_platform.h"
26
#include "env-inl.h"
27
#include "util-inl.h"
28
29
// cppgc platform init/shutdown like cctest does
30
#include "cppgc/platform.h"
31
32
#if defined(__GLIBC__)
33
#include <malloc.h>  // malloc_trim
34
#endif
35
36
namespace fuzz {
37
namespace {
38
39
// -------- Process-wide, persistent Node/V8 state (single Environment) --------
40
std::unique_ptr<node::NodePlatform> g_platform;
41
uv_loop_t                            g_persist_loop;
42
43
using ABAUnique =
44
    std::unique_ptr<node::ArrayBufferAllocator, decltype(&node::FreeArrayBufferAllocator)>;
45
46
ABAUnique                            g_persist_allocator{ nullptr, &node::FreeArrayBufferAllocator };
47
48
v8::Isolate*                         g_iso = nullptr;
49
v8::Global<v8::Context>              g_ctx;
50
node::IsolateData*                   g_iso_data = nullptr;
51
node::Environment*                   g_env = nullptr;
52
53
// Pre-compiled JS function: (a:ArrayBuffer, b:ArrayBuffer) => Buffer.compare(...)
54
v8::Global<v8::Function>             g_bufcmp_fn;
55
56
// Helper: run platform tasks + libuv + microtasks once.
57
// Returns true if any progress was made.
58
// NOTE: Callers must have entered the isolate (Isolate::Scope) before calling.
59
static inline bool OnePump(v8::Isolate* isolate,
60
                           node::NodePlatform* platform,
61
22.1k
                           uv_loop_t* loop) {
62
22.1k
  bool progressed = false;
63
22.1k
  platform->DrainTasks(isolate);
64
22.1k
  progressed |= (uv_run(loop, UV_RUN_NOWAIT) != 0);
65
22.1k
  isolate->PerformMicrotaskCheckpoint();
66
22.1k
  return progressed;
67
22.1k
}
68
69
// Drain up to max_spins or until the loop is idle.
70
// Enters the isolate to satisfy V8 invariants while touching microtasks/heap.
71
static inline void DrainUntilIdle(v8::Isolate* isolate,
72
                                  node::NodePlatform* platform,
73
                                  uv_loop_t* loop,
74
22.1k
                                  int max_spins = 256) {
75
22.1k
  v8::Isolate::Scope iso_scope(isolate);
76
22.1k
  v8::HandleScope hs(isolate);
77
22.1k
  for (int i = 0; i < max_spins; ++i) {
78
22.1k
    const bool progressed = OnePump(isolate, platform, loop);
79
22.1k
    if (!progressed && !uv_loop_alive(loop)) break;
80
22.1k
  }
81
22.1k
}
82
83
// Build (once) a comparator that DOES NOT copy: Buffer.from(ArrayBuffer) shares memory.
84
35
static void BuildBufCmpOnce() {
85
35
  if (!g_bufcmp_fn.IsEmpty()) return;
86
87
35
  v8::Isolate::Scope iso_scope(g_iso);
88
35
  v8::HandleScope hs(g_iso);
89
35
  v8::Local<v8::Context> ctx = g_ctx.Get(g_iso);
90
35
  v8::Context::Scope cs(ctx);
91
92
  // Share the underlying ArrayBuffer; avoid Uint8Array -> Buffer copy.
93
  // Docs: Buffer.from(arrayBuffer[, byteOffset[, length]]) shares memory.
94
35
  constexpr const char* kSrc = R"JS(
95
35
    (function(a, b) {
96
35
      const bufa = Buffer.from(a);
97
35
      const bufb = Buffer.from(b);
98
35
      return Buffer.compare(bufa, bufb);
99
35
    })
100
35
  )JS";
101
102
35
  v8::Local<v8::String> src;
103
35
  if (!v8::String::NewFromUtf8(g_iso, kSrc, v8::NewStringType::kNormal).ToLocal(&src)) return;
104
35
  v8::Local<v8::Script> script;
105
35
  if (!v8::Script::Compile(ctx, src).ToLocal(&script)) return;
106
35
  v8::Local<v8::Value> fn_val;
107
35
  if (!script->Run(ctx).ToLocal(&fn_val)) return;
108
35
  g_bufcmp_fn.Reset(g_iso, fn_val.As<v8::Function>());
109
35
}
110
111
35
void GlobalShutdown() {
112
35
  if (g_env != nullptr) {
113
35
    v8::Isolate::Scope iso_scope(g_iso);
114
35
    v8::HandleScope hs(g_iso);
115
35
    v8::Local<v8::Context> ctx = g_ctx.Get(g_iso);
116
35
    v8::Context::Scope cs(ctx);
117
118
35
    node::RunAtExit(g_env);
119
35
    node::Stop(g_env);
120
35
    DrainUntilIdle(g_iso, g_platform.get(), &g_persist_loop);
121
122
35
    node::FreeEnvironment(g_env);
123
35
    g_env = nullptr;
124
35
  }
125
126
35
  if (g_iso_data != nullptr) {
127
35
    node::FreeIsolateData(g_iso_data);
128
35
    g_iso_data = nullptr;
129
35
  }
130
131
35
  g_bufcmp_fn.Reset();
132
35
  g_ctx.Reset();
133
134
35
  if (g_iso != nullptr) {
135
    // Dispose the isolate via the platform so its per-isolate queues are freed.
136
35
    g_platform->DisposeIsolate(g_iso);
137
35
    g_iso = nullptr;
138
35
  }
139
140
  // Close the persistent libuv loop.
141
35
  uv_loop_close(&g_persist_loop);
142
143
  // Bring down cppgc + V8 + platform (order matters).
144
35
  g_platform->Shutdown();
145
35
  cppgc::ShutdownProcess();
146
35
  v8::V8::Dispose();
147
35
  v8::V8::DisposePlatform();
148
149
35
  g_platform.reset();
150
35
  g_persist_allocator.reset();
151
35
}
152
153
// Set up the persistent Environment once.
154
44.1k
static void InitializePersistentEnvOnce() {
155
44.1k
  if (g_env != nullptr) return;  // already initialized
156
157
35
  uv_os_unsetenv("NODE_OPTIONS");
158
159
  // Small, fast platform with no tracing.
160
35
  static constexpr int kV8ThreadPoolSize = 1;
161
35
  g_platform = std::make_unique<node::NodePlatform>(kV8ThreadPoolSize, /*tracing_controller=*/nullptr);
162
35
  v8::V8::InitializePlatform(g_platform.get());
163
164
  // Parse Node/V8 flags BEFORE V8::Initialize() to avoid "IsFrozen()" asserts.
165
35
  std::vector<std::string> node_argv{ "fuzz_env" };
166
35
  (void) node::InitializeOncePerProcess(
167
35
      node_argv,
168
35
      node::ProcessInitializationFlags::kLegacyInitializeNodeWithArgsBehavior);
169
170
  // Initialize cppgc + V8
171
35
  cppgc::InitializeProcess(g_platform->GetPageAllocator());
172
35
  v8::V8::Initialize();
173
174
  // Persistent libuv loop for this Environment.
175
35
  (void)uv_loop_init(&g_persist_loop);
176
177
  // Process-wide allocator for this persistent isolate.
178
35
  g_persist_allocator.reset(node::CreateArrayBufferAllocator());
179
180
  // Create isolate, context, and Node Environment.
181
35
  g_iso = node::NewIsolate(g_persist_allocator.get(), &g_persist_loop, g_platform.get());
182
35
  {
183
35
    v8::Isolate::Scope iso_scope(g_iso);
184
35
    v8::HandleScope hs(g_iso);
185
186
35
    v8::Local<v8::Context> ctx = node::NewContext(g_iso);
187
35
    g_ctx.Reset(g_iso, ctx);
188
35
    v8::Context::Scope cs(ctx);
189
190
35
    g_iso_data = node::CreateIsolateData(g_iso, &g_persist_loop, g_platform.get());
191
192
35
    std::vector<std::string> args{ "node" };
193
35
    std::vector<std::string> exec_args;
194
35
    node::EnvironmentFlags::Flags flags = node::EnvironmentFlags::kDefaultFlags;
195
196
35
    g_env = node::CreateEnvironment(g_iso_data, ctx, args, exec_args, flags);
197
198
    // Bootstrap Node (no entry script).
199
35
    node::LoadEnvironment(g_env, const_cast<char*>(""));
200
201
    // Build and cache the comparator function.
202
35
    BuildBufCmpOnce();
203
35
  }
204
205
  // Ensure we tear everything down at process exit.
206
35
  std::atexit(&GlobalShutdown);
207
35
}
208
209
}  // namespace
210
211
// ------------------------ IsolateScope (lightweight façade) --------------------
212
213
22.0k
IsolateScope::IsolateScope() {
214
  // Ensure persistent env/isolate exist, then "enter" the isolate so code that
215
  // expects an entered isolate continues to work. This does NOT own the isolate.
216
22.0k
  InitializePersistentEnvOnce();
217
22.0k
  isolate_ = g_iso;
218
22.0k
  if (isolate_) isolate_->Enter();
219
22.0k
}
220
221
22.0k
IsolateScope::~IsolateScope() {
222
22.0k
  if (!isolate_) return;
223
  // Leave the isolate; do NOT dispose it (persistent env owns it).
224
22.0k
  isolate_->Exit();
225
22.0k
  isolate_ = nullptr;
226
227
22.0k
#if defined(__GLIBC__)
228
  // Keep RSS in check during long runs.
229
22.0k
  malloc_trim(0);
230
22.0k
#endif
231
22.0k
}
232
233
// ----------------------- Public helpers (persistent env) -----------------------
234
235
void RunEnvString(v8::Isolate* /*unused*/,
236
                  const char* env_js,
237
8.61k
                  const EnvRunOptions& /*opts*/) {
238
8.61k
  InitializePersistentEnvOnce();
239
240
8.61k
  v8::Isolate::Scope iso_scope(g_iso);
241
8.61k
  v8::HandleScope hs(g_iso);
242
8.61k
  v8::Local<v8::Context> ctx = g_ctx.Get(g_iso);
243
8.61k
  v8::Context::Scope cs(ctx);
244
245
8.61k
  if (!env_js) env_js = "";
246
247
8.61k
  v8::TryCatch tc(g_iso);
248
8.61k
  v8::Local<v8::String> src;
249
8.61k
  if (v8::String::NewFromUtf8(g_iso, env_js, v8::NewStringType::kNormal).ToLocal(&src)) {
250
8.61k
    v8::Local<v8::Script> script;
251
8.61k
    if (v8::Script::Compile(ctx, src).ToLocal(&script)) {
252
0
      (void)script->Run(ctx);
253
0
    }
254
8.61k
  }
255
256
  // Keep the job stateless: drain until idle; do not Stop()/FreeEnvironment().
257
8.61k
  DrainUntilIdle(g_iso, g_platform.get(), &g_persist_loop);
258
8.61k
}
259
260
void RunInEnvironment(v8::Isolate* /*unused*/,
261
                      EnvCallback cb,
262
13.4k
                      const EnvRunOptions& /*opts*/) {
263
13.4k
  InitializePersistentEnvOnce();
264
265
13.4k
  v8::Isolate::Scope iso_scope(g_iso);
266
13.4k
  v8::HandleScope hs(g_iso);
267
13.4k
  v8::Local<v8::Context> ctx = g_ctx.Get(g_iso);
268
13.4k
  v8::Context::Scope cs(ctx);
269
270
13.4k
  cb(g_env, ctx);
271
13.4k
  DrainUntilIdle(g_iso, g_platform.get(), &g_persist_loop);
272
13.4k
}
273
274
}  // namespace fuzz