Skip to content

Commit 4d8456c

Browse files
committed
buffer: refactor zero-fill-field passing
Instead of storing the zero-fill-field as a fixed `ArrayBuffer` on the binding, only load it via a function call once bootstrapping is complete. This makes sure that the value is set correctly after loading from a snapshot (i.e. that the accessor for the field is stored in the snapshot, not the field itself).
1 parent 56f2556 commit 4d8456c

File tree

6 files changed

+56
-35
lines changed

6 files changed

+56
-35
lines changed

lib/buffer.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const {
5656
swap64: _swap64,
5757
kMaxLength,
5858
kStringMaxLength,
59-
zeroFill: bindingZeroFill
59+
getZeroFillField
6060
} = internalBinding('buffer');
6161
const {
6262
arraybuffer_untransferable_private_symbol,
@@ -131,18 +131,22 @@ const constants = ObjectDefineProperties({}, {
131131
}
132132
});
133133

134+
const encodingsMap = ObjectCreate(null);
135+
for (let i = 0; i < encodings.length; ++i)
136+
encodingsMap[encodings[i]] = i;
137+
134138
Buffer.poolSize = 8 * 1024;
135139
let poolSize, poolOffset, allocPool;
136140

137141
// A toggle used to access the zero fill setting of the array buffer allocator
138142
// in C++.
139-
// |zeroFill| can be undefined when running inside an isolate where we
140-
// do not own the ArrayBuffer allocator. Zero fill is always on in that case.
141-
const zeroFill = bindingZeroFill || [0];
142-
143-
const encodingsMap = ObjectCreate(null);
144-
for (let i = 0; i < encodings.length; ++i)
145-
encodingsMap[encodings[i]] = i;
143+
// getZeroFillField() can return undefined when running inside an isolate where
144+
// we do not own the ArrayBuffer allocator. Zero fill is always on in that case.
145+
let zeroFill = new Uint32Array([0]);
146+
function refreshZeroFillField() {
147+
const fromBinding = getZeroFillField();
148+
if (fromBinding) zeroFill = fromBinding;
149+
}
146150

147151
function createUnsafeBuffer(size) {
148152
zeroFill[0] = 0;
@@ -1207,7 +1211,9 @@ module.exports = {
12071211
transcode,
12081212
// Legacy
12091213
kMaxLength,
1210-
kStringMaxLength
1214+
kStringMaxLength,
1215+
// Called and deleted by internal/bootstrap/pre_execution.js.
1216+
refreshZeroFillField
12111217
};
12121218

12131219
ObjectDefineProperties(module.exports, {

lib/internal/bootstrap/node.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ function setupBuffer() {
312312
// Only after this point can C++ use Buffer::New()
313313
bufferBinding.setBufferPrototype(Buffer.prototype);
314314
delete bufferBinding.setBufferPrototype;
315-
delete bufferBinding.zeroFill;
315+
delete bufferBinding.getZeroFillField;
316316

317317
ObjectDefineProperty(global, 'Buffer', {
318318
value: Buffer,

lib/internal/bootstrap/pre_execution.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ const {
77
} = primordials;
88

99
const { getOptionValue } = require('internal/options');
10-
const { Buffer } = require('buffer');
10+
const buffer = require('buffer');
11+
const { Buffer } = buffer;
1112
const { ERR_MANIFEST_ASSERT_INTEGRITY } = require('internal/errors').codes;
1213
const assert = require('internal/assert');
1314

@@ -107,6 +108,9 @@ function patchProcessObject(expandArgv1) {
107108
addReadOnlyProcessAlias('traceDeprecation', '--trace-deprecation');
108109
addReadOnlyProcessAlias('_breakFirstLine', '--inspect-brk', false);
109110
addReadOnlyProcessAlias('_breakNodeFirstLine', '--inspect-brk-node', false);
111+
112+
buffer.refreshZeroFillField();
113+
delete buffer.refreshZeroFillField;
110114
}
111115

112116
function addReadOnlyProcessAlias(name, option, enumerable = true) {

src/api/environment.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace node {
1515
using errors::TryCatchScope;
1616
using v8::Array;
17+
using v8::ArrayBuffer;
1718
using v8::Context;
1819
using v8::EscapableHandleScope;
1920
using v8::FinalizationGroup;
@@ -91,6 +92,17 @@ static void HostCleanupFinalizationGroupCallback(
9192
env->RegisterFinalizationGroupForCleanup(group);
9293
}
9394

95+
std::shared_ptr<v8::BackingStore> NodeArrayBufferAllocator::zero_fill_field() {
96+
if (!zero_fill_field_bs_) {
97+
zero_fill_field_bs_ =
98+
ArrayBuffer::NewBackingStore(&zero_fill_field_,
99+
sizeof(zero_fill_field_),
100+
[](void*, size_t, void*){},
101+
nullptr);
102+
}
103+
return zero_fill_field_bs_;
104+
}
105+
94106
void* NodeArrayBufferAllocator::Allocate(size_t size) {
95107
void* ret;
96108
if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers)

src/node_buffer.cc

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,13 +1152,33 @@ void SetBufferPrototype(const FunctionCallbackInfo<Value>& args) {
11521152
}
11531153

11541154

1155+
void GetZeroFillField(const FunctionCallbackInfo<Value>& args) {
1156+
Environment* env = Environment::GetCurrent(args);
1157+
1158+
// This can be a nullptr when running inside an isolate where we
1159+
// do not own the ArrayBuffer allocator.
1160+
NodeArrayBufferAllocator* allocator = env->isolate_data()->node_allocator();
1161+
if (allocator == nullptr) return;
1162+
1163+
std::shared_ptr<v8::BackingStore> backing = allocator->zero_fill_field();
1164+
Local<ArrayBuffer> array_buffer =
1165+
ArrayBuffer::New(env->isolate(), std::move(backing));
1166+
array_buffer->SetPrivate(
1167+
env->context(),
1168+
env->arraybuffer_untransferable_private_symbol(),
1169+
True(env->isolate())).Check();
1170+
args.GetReturnValue().Set(Uint32Array::New(array_buffer, 0, 1));
1171+
}
1172+
1173+
11551174
void Initialize(Local<Object> target,
11561175
Local<Value> unused,
11571176
Local<Context> context,
11581177
void* priv) {
11591178
Environment* env = Environment::GetCurrent(context);
11601179

11611180
env->SetMethod(target, "setBufferPrototype", SetBufferPrototype);
1181+
env->SetMethod(target, "getZeroFillField", GetZeroFillField);
11621182
env->SetMethodNoSideEffect(target, "createFromString", CreateFromString);
11631183

11641184
env->SetMethodNoSideEffect(target, "byteLengthUtf8", ByteLengthUtf8);
@@ -1198,34 +1218,12 @@ void Initialize(Local<Object> target,
11981218
env->SetMethod(target, "hexWrite", StringWrite<HEX>);
11991219
env->SetMethod(target, "ucs2Write", StringWrite<UCS2>);
12001220
env->SetMethod(target, "utf8Write", StringWrite<UTF8>);
1201-
1202-
// It can be a nullptr when running inside an isolate where we
1203-
// do not own the ArrayBuffer allocator.
1204-
if (NodeArrayBufferAllocator* allocator =
1205-
env->isolate_data()->node_allocator()) {
1206-
uint32_t* zero_fill_field = allocator->zero_fill_field();
1207-
std::unique_ptr<BackingStore> backing =
1208-
ArrayBuffer::NewBackingStore(zero_fill_field,
1209-
sizeof(*zero_fill_field),
1210-
[](void*, size_t, void*){},
1211-
nullptr);
1212-
Local<ArrayBuffer> array_buffer =
1213-
ArrayBuffer::New(env->isolate(), std::move(backing));
1214-
array_buffer->SetPrivate(
1215-
env->context(),
1216-
env->arraybuffer_untransferable_private_symbol(),
1217-
True(env->isolate())).Check();
1218-
CHECK(target
1219-
->Set(env->context(),
1220-
FIXED_ONE_BYTE_STRING(env->isolate(), "zeroFill"),
1221-
Uint32Array::New(array_buffer, 0, 1))
1222-
.FromJust());
1223-
}
12241221
}
12251222

12261223
static ExternalReferences external_references {
12271224
__FILE__,
12281225
SetBufferPrototype,
1226+
GetZeroFillField,
12291227
CreateFromString,
12301228
ByteLengthUtf8,
12311229
Copy,

src/node_internals.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ void PromiseRejectCallback(v8::PromiseRejectMessage message);
107107

108108
class NodeArrayBufferAllocator : public ArrayBufferAllocator {
109109
public:
110-
inline uint32_t* zero_fill_field() { return &zero_fill_field_; }
110+
std::shared_ptr<v8::BackingStore> zero_fill_field();
111111

112112
void* Allocate(size_t size) override; // Defined in src/node.cc
113113
void* AllocateUninitialized(size_t size) override;
@@ -127,6 +127,7 @@ class NodeArrayBufferAllocator : public ArrayBufferAllocator {
127127

128128
private:
129129
uint32_t zero_fill_field_ = 1; // Boolean but exposed as uint32 to JS land.
130+
std::shared_ptr<v8::BackingStore> zero_fill_field_bs_;
130131
std::atomic<size_t> total_mem_usage_ {0};
131132
};
132133

0 commit comments

Comments
 (0)