Skip to content

Commit b88ee54

Browse files
bnoordhuisdanielleadams
authored andcommitted
src: make structuredClone work for process.env
Fixes: #45380 PR-URL: #45698 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 1019209 commit b88ee54

7 files changed

+79
-12
lines changed

src/env_properties.h

+2
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@
334334
V(contextify_global_template, v8::ObjectTemplate) \
335335
V(contextify_wrapper_template, v8::ObjectTemplate) \
336336
V(compiled_fn_entry_template, v8::ObjectTemplate) \
337+
V(env_proxy_template, v8::ObjectTemplate) \
338+
V(env_proxy_ctor_template, v8::FunctionTemplate) \
337339
V(dir_instance_template, v8::ObjectTemplate) \
338340
V(fd_constructor_template, v8::ObjectTemplate) \
339341
V(fdclose_constructor_template, v8::ObjectTemplate) \

src/node_env_var.cc

+30-5
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ using v8::Boolean;
1313
using v8::Context;
1414
using v8::DontDelete;
1515
using v8::DontEnum;
16-
using v8::EscapableHandleScope;
16+
using v8::FunctionTemplate;
1717
using v8::HandleScope;
1818
using v8::Integer;
1919
using v8::Isolate;
@@ -316,6 +316,26 @@ Maybe<bool> KVStore::AssignFromObject(Local<Context> context,
316316
return Just(true);
317317
}
318318

319+
// TODO(bnoordhuis) Not super efficient but called infrequently. Not worth
320+
// the trouble yet of specializing for RealEnvStore and MapKVStore.
321+
Maybe<bool> KVStore::AssignToObject(v8::Isolate* isolate,
322+
v8::Local<v8::Context> context,
323+
v8::Local<v8::Object> object) {
324+
HandleScope scope(isolate);
325+
Local<Array> keys = Enumerate(isolate);
326+
uint32_t keys_length = keys->Length();
327+
for (uint32_t i = 0; i < keys_length; i++) {
328+
Local<Value> key;
329+
Local<String> value;
330+
bool ok = keys->Get(context, i).ToLocal(&key);
331+
ok = ok && key->IsString();
332+
ok = ok && Get(isolate, key.As<String>()).ToLocal(&value);
333+
ok = ok && object->Set(context, key, value).To(&ok);
334+
if (!ok) return Nothing<bool>();
335+
}
336+
return Just(true);
337+
}
338+
319339
static void EnvGetter(Local<Name> property,
320340
const PropertyCallbackInfo<Value>& info) {
321341
Environment* env = Environment::GetCurrent(info);
@@ -436,9 +456,13 @@ static void EnvDefiner(Local<Name> property,
436456
}
437457
}
438458

439-
MaybeLocal<Object> CreateEnvVarProxy(Local<Context> context, Isolate* isolate) {
440-
EscapableHandleScope scope(isolate);
441-
Local<ObjectTemplate> env_proxy_template = ObjectTemplate::New(isolate);
459+
void CreateEnvProxyTemplate(Isolate* isolate, IsolateData* isolate_data) {
460+
HandleScope scope(isolate);
461+
if (!isolate_data->env_proxy_template().IsEmpty()) return;
462+
Local<FunctionTemplate> env_proxy_ctor_template =
463+
FunctionTemplate::New(isolate);
464+
Local<ObjectTemplate> env_proxy_template =
465+
ObjectTemplate::New(isolate, env_proxy_ctor_template);
442466
env_proxy_template->SetHandler(NamedPropertyHandlerConfiguration(
443467
EnvGetter,
444468
EnvSetter,
@@ -449,7 +473,8 @@ MaybeLocal<Object> CreateEnvVarProxy(Local<Context> context, Isolate* isolate) {
449473
nullptr,
450474
Local<Value>(),
451475
PropertyHandlerFlags::kHasNoSideEffect));
452-
return scope.EscapeMaybe(env_proxy_template->NewInstance(context));
476+
isolate_data->set_env_proxy_template(env_proxy_template);
477+
isolate_data->set_env_proxy_ctor_template(env_proxy_ctor_template);
453478
}
454479

455480
void RegisterEnvVarExternalReferences(ExternalReferenceRegistry* registry) {

src/node_messaging.cc

+30-2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ namespace node {
4242

4343
using BaseObjectList = std::vector<BaseObjectPtr<BaseObject>>;
4444

45+
// Hack to have WriteHostObject inform ReadHostObject that the value
46+
// should be treated as a regular JS object. Used to transfer process.env.
47+
static const uint32_t kNormalObject = static_cast<uint32_t>(-1);
48+
4549
BaseObject::TransferMode BaseObject::GetTransferMode() const {
4650
return BaseObject::TransferMode::kUntransferable;
4751
}
@@ -98,8 +102,17 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
98102
uint32_t id;
99103
if (!deserializer->ReadUint32(&id))
100104
return MaybeLocal<Object>();
101-
CHECK_LT(id, host_objects_.size());
102-
return host_objects_[id]->object(isolate);
105+
if (id != kNormalObject) {
106+
CHECK_LT(id, host_objects_.size());
107+
return host_objects_[id]->object(isolate);
108+
}
109+
EscapableHandleScope scope(isolate);
110+
Local<Context> context = isolate->GetCurrentContext();
111+
Local<Value> object;
112+
if (!deserializer->ReadValue(context).ToLocal(&object))
113+
return MaybeLocal<Object>();
114+
CHECK(object->IsObject());
115+
return scope.Escape(object.As<Object>());
103116
}
104117

105118
MaybeLocal<SharedArrayBuffer> GetSharedArrayBufferFromId(
@@ -293,6 +306,21 @@ class SerializerDelegate : public ValueSerializer::Delegate {
293306
BaseObjectPtr<BaseObject> { Unwrap<BaseObject>(object) });
294307
}
295308

309+
// Convert process.env to a regular object.
310+
auto env_proxy_ctor_template = env_->env_proxy_ctor_template();
311+
if (!env_proxy_ctor_template.IsEmpty() &&
312+
env_proxy_ctor_template->HasInstance(object)) {
313+
HandleScope scope(isolate);
314+
// TODO(bnoordhuis) Prototype-less object in case process.env contains
315+
// a "__proto__" key? process.env has a prototype with concomitant
316+
// methods like toString(). It's probably confusing if that gets lost
317+
// in transmission.
318+
Local<Object> normal_object = Object::New(isolate);
319+
env_->env_vars()->AssignToObject(isolate, env_->context(), normal_object);
320+
serializer->WriteUint32(kNormalObject); // Instead of a BaseObject.
321+
return serializer->WriteValue(env_->context(), normal_object);
322+
}
323+
296324
ThrowDataCloneError(env_->clone_unsupported_type_str());
297325
return Nothing<bool>();
298326
}

src/node_process.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@
1010
namespace node {
1111

1212
class Environment;
13+
class IsolateData;
1314
class MemoryTracker;
1415
class ExternalReferenceRegistry;
1516
class Realm;
1617

17-
v8::MaybeLocal<v8::Object> CreateEnvVarProxy(v8::Local<v8::Context> context,
18-
v8::Isolate* isolate);
18+
void CreateEnvProxyTemplate(v8::Isolate* isolate, IsolateData* isolate_data);
1919

2020
// Most of the time, it's best to use `console.error` to write
2121
// to the process.stderr stream. However, in some cases, such as

src/node_realm.cc

+4-3
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,10 @@ MaybeLocal<Value> Realm::BootstrapNode() {
275275
}
276276

277277
Local<String> env_string = FIXED_ONE_BYTE_STRING(isolate_, "env");
278-
Local<Object> env_var_proxy;
279-
if (!CreateEnvVarProxy(context(), isolate_).ToLocal(&env_var_proxy) ||
280-
process_object()->Set(context(), env_string, env_var_proxy).IsNothing()) {
278+
Local<Object> env_proxy;
279+
CreateEnvProxyTemplate(isolate_, env_->isolate_data());
280+
if (!env_->env_proxy_template()->NewInstance(context()).ToLocal(&env_proxy) ||
281+
process_object()->Set(context(), env_string, env_proxy).IsNothing()) {
281282
return MaybeLocal<Value>();
282283
}
283284

src/util.h

+3
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ class KVStore {
297297
virtual std::shared_ptr<KVStore> Clone(v8::Isolate* isolate) const;
298298
virtual v8::Maybe<bool> AssignFromObject(v8::Local<v8::Context> context,
299299
v8::Local<v8::Object> entries);
300+
v8::Maybe<bool> AssignToObject(v8::Isolate* isolate,
301+
v8::Local<v8::Context> context,
302+
v8::Local<v8::Object> object);
300303

301304
static std::shared_ptr<KVStore> CreateMapKVStore();
302305
};

test/parallel/test-process-env.js

+8
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ if (common.isWindows) {
105105
assert.ok(keys.length > 0);
106106
}
107107

108+
// https://github.com/nodejs/node/issues/45380
109+
{
110+
const env = structuredClone(process.env);
111+
// deepEqual(), not deepStrictEqual(), because of different prototypes.
112+
// eslint-disable-next-line no-restricted-properties
113+
assert.deepEqual(env, process.env);
114+
}
115+
108116
// Setting environment variables on Windows with empty names should not cause
109117
// an assertion failure.
110118
// https://github.com/nodejs/node/issues/32920

0 commit comments

Comments
 (0)