Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,9 @@ added:

Disable the `Object.prototype.__proto__` property. If `mode` is `delete`, the
property is removed entirely. If `mode` is `throw`, accesses to the
property throw an exception with the code `ERR_PROTO_ACCESS`.
property throw an exception with the code `ERR_PROTO_ACCESS`. If `mode` is
`warn`, accesses to the property emit a deprecation warning while preserving
existing behavior.

### `--disable-sigusr1`

Expand Down
51 changes: 51 additions & 0 deletions src/api/environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,48 @@ void ProtoThrower(const FunctionCallbackInfo<Value>& info) {
THROW_ERR_PROTO_ACCESS(info.GetIsolate());
}

void EmitProtoWarn(Isolate* isolate) {
Local<Context> context = isolate->GetCurrentContext();
Environment* env = Environment::GetCurrent(context);
if (env->options()->pending_deprecation && env->EmitProtoWarning()) {
if (ProcessEmitDeprecationWarning(
env,
"Object.prototype.__proto__ is deprecated and will be removed in a future"
" version of Node.js. Use Object.getPrototypeOf() and "
"Object.setPrototypeOf() instead.",
"DeprecationWarning").IsNothing())
return;
}
}

void ProtoGetterWarn(Local<Name> property,
const PropertyCallbackInfo<Value>& info) {
Isolate* isolate = info.GetIsolate();
EmitProtoWarn(isolate);

Local<Context> context = isolate->GetCurrentContext();
Local<Value> receiver = info.This();

Local<Value> prototype;
if (!receiver->ToObject(context).ToLocalChecked()
->GetPrototypeV2()
.ToLocal(&prototype)) {
return;
}

info.GetReturnValue().Set(prototype);
}

void ProtoSetterWarn(Local<Name> property,
Local<Value> value,
const PropertyCallbackInfo<void>& info) {
Isolate* isolate = info.GetIsolate();
EmitProtoWarn(isolate);

Local<Context> context = isolate->GetCurrentContext();
USE(info.This()->SetPrototypeV2(context, value));
}

// This runs at runtime, regardless of whether the context
// is created from a snapshot.
Maybe<void> InitializeContextRuntime(Local<Context> context) {
Expand Down Expand Up @@ -861,6 +903,15 @@ Maybe<void> InitializeContextRuntime(Local<Context> context) {
.IsNothing()) {
return Nothing<void>();
}
} else if (per_process::cli_options->disable_proto == "warn") {
PropertyDescriptor descriptor(ProtoGetterWarn, ProtoSetterWarn);
descriptor.set_enumerable(false);
descriptor.set_configurable(true);
if (prototype
->DefineProperty(context, proto_string, descriptor)
.IsNothing()) {
return Nothing<void>();
}
} else if (per_process::cli_options->disable_proto != "") {
// Validated in ProcessGlobalArgs
UNREACHABLE("invalid --disable-proto mode");
Expand Down
7 changes: 7 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,12 @@ class Environment final : public MemoryRetainer {
return current_value;
}

inline bool EmitProtoWarning() {
bool current_value = emit_proto_warning_;
emit_proto_warning_ = false;
return current_value;
}

// cb will be called as cb(env) on the next event loop iteration.
// Unlike the JS setImmediate() function, nested SetImmediate() calls will
// be run without returning control to the event loop, similar to nextTick().
Expand Down Expand Up @@ -1117,6 +1123,7 @@ class Environment final : public MemoryRetainer {
bool trace_sync_io_ = false;
bool emit_env_nonstring_warning_ = true;
bool emit_err_name_warning_ = true;
bool emit_proto_warning_ = true;
bool source_maps_enabled_ = false;

size_t async_callback_scope_depth_ = 0;
Expand Down
1 change: 1 addition & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,7 @@ static ExitCode ProcessGlobalArgsInternal(std::vector<std::string>* args,

if (per_process::cli_options->disable_proto != "delete" &&
per_process::cli_options->disable_proto != "throw" &&
per_process::cli_options->disable_proto != "warn" &&
per_process::cli_options->disable_proto != "") {
errors->emplace_back("invalid mode passed to --disable-proto");
// TODO(joyeecheung): merge into kInvalidCommandLineArgument.
Expand Down
46 changes: 46 additions & 0 deletions test/parallel/test-disable-proto-warn.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Flags: --disable-proto=warn

'use strict';

const common = require('../common');
const assert = require('assert');
const vm = require('vm');
const { Worker, isMainThread } = require('worker_threads');

common.expectWarning(
'DeprecationWarning',
'Object.prototype.__proto__ is deprecated and will be removed in a future ' +
'version of Node.js. Use Object.getPrototypeOf() and ' +
'Object.setPrototypeOf() instead.'
);

// eslint-disable-next-line no-proto
const proto1 = ({}).__proto__;
assert.strictEqual(proto1, Object.prototype);

const obj = {};
const newProto = { a: 1 };
// eslint-disable-next-line no-proto
obj.__proto__ = newProto;
assert.strictEqual(Object.getPrototypeOf(obj), newProto);

const ctx = vm.createContext();
assert.strictEqual(
vm.runInContext('({}).__proto__ === Object.prototype', ctx),
true
);
assert.strictEqual(
vm.runInContext(`
const obj = {};
const newProto = { a: 1 };
obj.__proto__ = newProto;
Object.getPrototypeOf(obj) === newProto;
`, ctx),
true
);

if (isMainThread) {
new Worker(__filename);
} else {
process.exit();
}