Skip to content

Commit f20e814

Browse files
committed
src: make structuredClone work for process.env
Fixes: #45380
1 parent 3bed5f1 commit f20e814

File tree

7 files changed

+74
-12
lines changed

7 files changed

+74
-12
lines changed

src/env_properties.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@
333333
V(contextify_global_template, v8::ObjectTemplate) \
334334
V(contextify_wrapper_template, v8::ObjectTemplate) \
335335
V(compiled_fn_entry_template, v8::ObjectTemplate) \
336+
V(env_proxy_template, v8::ObjectTemplate) \
337+
V(env_proxy_ctor_template, v8::FunctionTemplate) \
336338
V(dir_instance_template, v8::ObjectTemplate) \
337339
V(fd_constructor_template, v8::ObjectTemplate) \
338340
V(fdclose_constructor_template, v8::ObjectTemplate) \

src/node_env_var.cc

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ using v8::Context;
1414
using v8::DontDelete;
1515
using v8::DontEnum;
1616
using v8::EscapableHandleScope;
17+
using v8::FunctionTemplate;
1718
using v8::HandleScope;
1819
using v8::Integer;
1920
using v8::Isolate;
@@ -316,6 +317,24 @@ Maybe<bool> KVStore::AssignFromObject(Local<Context> context,
316317
return Just(true);
317318
}
318319

320+
// TODO(bnoordhuis) Not super efficient but called infrequently. Not worth
321+
// the trouble yet of specializing for RealEnvStore and MapKVStore.
322+
void KVStore::AssignToObject(v8::Isolate* isolate,
323+
v8::Local<v8::Context> context,
324+
v8::Local<v8::Object> object) {
325+
HandleScope scope(isolate);
326+
Local<Array> keys = Enumerate(isolate);
327+
uint32_t keys_length = keys->Length();
328+
for (uint32_t i = 0; i < keys_length; i++) {
329+
Local<Value> key;
330+
Local<String> value;
331+
if (!keys->Get(context, i).ToLocal(&key)) continue;
332+
if (!key->IsString()) continue;
333+
if (!Get(isolate, key.As<String>()).ToLocal(&value)) continue;
334+
USE(object->Set(context, key, value));
335+
}
336+
}
337+
319338
static void EnvGetter(Local<Name> property,
320339
const PropertyCallbackInfo<Value>& info) {
321340
Environment* env = Environment::GetCurrent(info);
@@ -436,9 +455,13 @@ static void EnvDefiner(Local<Name> property,
436455
}
437456
}
438457

439-
MaybeLocal<Object> CreateEnvVarProxy(Local<Context> context, Isolate* isolate) {
440-
EscapableHandleScope scope(isolate);
441-
Local<ObjectTemplate> env_proxy_template = ObjectTemplate::New(isolate);
458+
void CreateEnvProxyTemplate(Isolate* isolate, IsolateData* isolate_data) {
459+
HandleScope scope(isolate);
460+
if (!isolate_data->env_proxy_template().IsEmpty()) return;
461+
Local<FunctionTemplate> env_proxy_ctor_template =
462+
FunctionTemplate::New(isolate);
463+
Local<ObjectTemplate> env_proxy_template =
464+
ObjectTemplate::New(isolate, env_proxy_ctor_template);
442465
env_proxy_template->SetHandler(NamedPropertyHandlerConfiguration(
443466
EnvGetter,
444467
EnvSetter,
@@ -449,7 +472,8 @@ MaybeLocal<Object> CreateEnvVarProxy(Local<Context> context, Isolate* isolate) {
449472
nullptr,
450473
Local<Value>(),
451474
PropertyHandlerFlags::kHasNoSideEffect));
452-
return scope.EscapeMaybe(env_proxy_template->NewInstance(context));
475+
isolate_data->set_env_proxy_template(env_proxy_template);
476+
isolate_data->set_env_proxy_ctor_template(env_proxy_ctor_template);
453477
}
454478

455479
void RegisterEnvVarExternalReferences(ExternalReferenceRegistry* registry) {

src/node_messaging.cc

Lines changed: 29 additions & 3 deletions
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
}
@@ -89,7 +93,8 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
8993
const std::vector<BaseObjectPtr<BaseObject>>& host_objects,
9094
const std::vector<Local<SharedArrayBuffer>>& shared_array_buffers,
9195
const std::vector<CompiledWasmModule>& wasm_modules)
92-
: host_objects_(host_objects),
96+
: env_(env),
97+
host_objects_(host_objects),
9398
shared_array_buffers_(shared_array_buffers),
9499
wasm_modules_(wasm_modules) {}
95100

@@ -98,8 +103,15 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
98103
uint32_t id;
99104
if (!deserializer->ReadUint32(&id))
100105
return MaybeLocal<Object>();
101-
CHECK_LT(id, host_objects_.size());
102-
return host_objects_[id]->object(isolate);
106+
if (id != kNormalObject) {
107+
CHECK_LT(id, host_objects_.size());
108+
return host_objects_[id]->object(isolate);
109+
}
110+
Local<Value> object;
111+
if (!deserializer->ReadValue(env_->context()).ToLocal(&object))
112+
return MaybeLocal<Object>();
113+
CHECK(object->IsObject());
114+
return object.As<Object>();
103115
}
104116

105117
MaybeLocal<SharedArrayBuffer> GetSharedArrayBufferFromId(
@@ -118,6 +130,7 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
118130
ValueDeserializer* deserializer = nullptr;
119131

120132
private:
133+
Environment* const env_;
121134
const std::vector<BaseObjectPtr<BaseObject>>& host_objects_;
122135
const std::vector<Local<SharedArrayBuffer>>& shared_array_buffers_;
123136
const std::vector<CompiledWasmModule>& wasm_modules_;
@@ -293,6 +306,19 @@ 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?
316+
Local<Object> normal_object = Object::New(isolate);
317+
env_->env_vars()->AssignToObject(isolate, env_->context(), normal_object);
318+
serializer->WriteUint32(kNormalObject); // Instead of a BaseObject.
319+
return serializer->WriteValue(env_->context(), normal_object);
320+
}
321+
296322
ThrowDataCloneError(env_->clone_unsupported_type_str());
297323
return Nothing<bool>();
298324
}

src/node_process.h

Lines changed: 2 additions & 2 deletions
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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,10 @@ MaybeLocal<Value> Realm::BootstrapNode() {
243243
}
244244

245245
Local<String> env_string = FIXED_ONE_BYTE_STRING(isolate_, "env");
246-
Local<Object> env_var_proxy;
247-
if (!CreateEnvVarProxy(context(), isolate_).ToLocal(&env_var_proxy) ||
248-
process_object()->Set(context(), env_string, env_var_proxy).IsNothing()) {
246+
Local<Object> env_proxy;
247+
CreateEnvProxyTemplate(isolate_, env_->isolate_data());
248+
if (!env_->env_proxy_template()->NewInstance(context()).ToLocal(&env_proxy) ||
249+
process_object()->Set(context(), env_string, env_proxy).IsNothing()) {
249250
return MaybeLocal<Value>();
250251
}
251252

src/util.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,9 @@ class KVStore {
298298
virtual std::shared_ptr<KVStore> Clone(v8::Isolate* isolate) const;
299299
virtual v8::Maybe<bool> AssignFromObject(v8::Local<v8::Context> context,
300300
v8::Local<v8::Object> entries);
301+
virtual void AssignToObject(v8::Isolate* isolate,
302+
v8::Local<v8::Context> context,
303+
v8::Local<v8::Object> object);
301304

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

test/parallel/test-process-env.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ 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+
assert.deepEqual(env, process.env);
112+
}
113+
108114
// Setting environment variables on Windows with empty names should not cause
109115
// an assertion failure.
110116
// https://github.com/nodejs/node/issues/32920

0 commit comments

Comments
 (0)