summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuy Bedford <guybedford@gmail.com>2019-01-24 20:56:33 +0200
committerRuben Bridgewater <ruben@bridgewater.de>2019-03-14 17:15:27 +0100
commit3e54f909118bdf763aee6f0ae11643d37d5eb0b9 (patch)
tree26439509f3d32c66f18fee78f14fa38e406606dc
parent1c8076ef58863bfd966a6b66c2b31939cc603a0f (diff)
downloadnode-new-3e54f909118bdf763aee6f0ae11643d37d5eb0b9.tar.gz
bootstrap: experimental --frozen-intrinsics flag
PR-URL: https://github.com/nodejs/node/pull/25685 Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
-rw-r--r--LICENSE18
-rw-r--r--doc/api/cli.md20
-rw-r--r--doc/node.13
-rw-r--r--lib/internal/bootstrap/pre_execution.js10
-rw-r--r--lib/internal/freeze_intrinsics.js244
-rw-r--r--lib/internal/main/worker_thread.js2
-rw-r--r--node.gyp1
-rw-r--r--src/node_options.cc4
-rw-r--r--src/node_options.h1
-rw-r--r--test/parallel/test-freeze-intrinsics.js10
-rwxr-xr-xtools/license-builder.sh3
11 files changed, 316 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
index 7bdbb07eae..2831dc3b5e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1360,6 +1360,24 @@ The externally maintained libraries used by Node.js are:
OR OTHER DEALINGS IN THE SOFTWARE.
"""
+- caja, located at lib/internal/freeze_intrinsics.js, is licensed as follows:
+ """
+ Adapted from SES/Caja - Copyright (C) 2011 Google Inc.
+ Copyright (C) 2018 Agoric
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ """
+
- brotli, located at deps/brotli, is licensed as follows:
"""
Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors.
diff --git a/doc/api/cli.md b/doc/api/cli.md
index 71e36a4c12..831b9624d1 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -177,6 +177,25 @@ added: v6.0.0
Force FIPS-compliant crypto on startup. (Cannot be disabled from script code.)
(Same requirements as `--enable-fips`.)
+### `--frozen-intrinsics`
+<!-- YAML
+added: REPLACEME
+-->
+
+> Stability: 1 - Experimental
+
+Enable experimental frozen intrinsics like `Array` and `Object`.
+
+Support is currently only provided for the root context and no guarantees are
+currently provided that `global.Array` is indeed the default intrinsic
+reference.
+
+**Code breakage is highly likely with this flag**, especially since limited
+support for subclassing builtins is provided currently due to ECMA-262 bug
+https://github.com/tc39/ecma262/pull/1320.
+
+Both of the above may change in future updates, which will be breaking changes.
+
### `--http-parser=library`
<!-- YAML
added: v11.4.0
@@ -671,6 +690,7 @@ Node.js options that are allowed are:
- `--experimental-report`
- `--experimental-vm-modules`
- `--force-fips`
+- `--frozen-intrinsics`
- `--icu-data-dir`
- `--inspect`
- `--inspect-brk`
diff --git a/doc/node.1 b/doc/node.1
index c0bdeb1beb..603d72d566 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -144,6 +144,9 @@ Force FIPS-compliant crypto on startup
Same requirements as
.Fl -enable-fips .
.
+.It Fl -frozen-intrinsics
+Enable experimental frozen intrinsics support.
+.
.It Fl -http-parser Ns = Ns Ar library
Chooses an HTTP parser library. Available values are
.Sy llhttp
diff --git a/lib/internal/bootstrap/pre_execution.js b/lib/internal/bootstrap/pre_execution.js
index 3e64540aa3..02acf6b46f 100644
--- a/lib/internal/bootstrap/pre_execution.js
+++ b/lib/internal/bootstrap/pre_execution.js
@@ -31,6 +31,7 @@ function prepareMainThreadExecution() {
initializeClusterIPC();
initializeDeprecations();
+ initializeFrozenIntrinsics();
initializeESMLoader();
loadPreloadModules();
}
@@ -230,6 +231,14 @@ function initializeESMLoader() {
}
}
+function initializeFrozenIntrinsics() {
+ if (getOptionValue('--frozen-intrinsics')) {
+ process.emitWarning('The --frozen-intrinsics flag is experimental',
+ 'ExperimentalWarning');
+ require('internal/freeze_intrinsics')();
+ }
+}
+
function loadPreloadModules() {
// For user code, we preload modules if `-r` is passed
const preloadModules = getOptionValue('--require');
@@ -245,6 +254,7 @@ module.exports = {
prepareMainThreadExecution,
initializeDeprecations,
initializeESMLoader,
+ initializeFrozenIntrinsics,
loadPreloadModules,
setupTraceCategoryState,
initializeReport
diff --git a/lib/internal/freeze_intrinsics.js b/lib/internal/freeze_intrinsics.js
new file mode 100644
index 0000000000..effac31853
--- /dev/null
+++ b/lib/internal/freeze_intrinsics.js
@@ -0,0 +1,244 @@
+// Adapted from SES/Caja - Copyright (C) 2011 Google Inc.
+// Copyright (C) 2018 Agoric
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// SPDX-License-Identifier: MIT
+
+// based upon:
+// https://github.com/google/caja/blob/master/src/com/google/caja/ses/startSES.js
+// https://github.com/google/caja/blob/master/src/com/google/caja/ses/repairES5.js
+// https://github.com/tc39/proposal-frozen-realms/blob/91ac390e3451da92b5c27e354b39e52b7636a437/shim/src/deep-freeze.js
+
+/* global WebAssembly, SharedArrayBuffer, console */
+'use strict';
+module.exports = function() {
+
+ const intrinsics = [
+ // Anonymous Intrinsics
+ // ThrowTypeError
+ Object.getOwnPropertyDescriptor(Function.prototype, 'caller').get,
+ // IteratorPrototype
+ Object.getPrototypeOf(
+ Object.getPrototypeOf(new Array()[Symbol.iterator]())
+ ),
+ // ArrayIteratorPrototype
+ Object.getPrototypeOf(new Array()[Symbol.iterator]()),
+ // StringIteratorPrototype
+ Object.getPrototypeOf(new String()[Symbol.iterator]()),
+ // MapIteratorPrototype
+ Object.getPrototypeOf(new Map()[Symbol.iterator]()),
+ // SetIteratorPrototype
+ Object.getPrototypeOf(new Set()[Symbol.iterator]()),
+ // GeneratorFunction
+ Object.getPrototypeOf(function* () {}),
+ // AsyncFunction
+ Object.getPrototypeOf(async function() {}),
+ // AsyncGeneratorFunction
+ Object.getPrototypeOf(async function* () {}),
+ // TypedArray
+ Object.getPrototypeOf(Uint8Array),
+
+ // 18 The Global Object
+ eval,
+ isFinite,
+ isNaN,
+ parseFloat,
+ parseInt,
+ decodeURI,
+ decodeURIComponent,
+ encodeURI,
+ encodeURIComponent,
+
+ // 19 Fundamental Objects
+ Object, // 19.1
+ Function, // 19.2
+ Boolean, // 19.3
+ Symbol, // 19.4
+
+ // Disabled pending stack trace mutation handling
+ // Error, // 19.5
+ // EvalError,
+ // RangeError,
+ // ReferenceError,
+ // SyntaxError,
+ // TypeError,
+ // URIError,
+
+ // 20 Numbers and Dates
+ Number, // 20.1
+ Math, // 20.2
+ Date, // 20.3
+
+ // 21 Text Processing
+ String, // 21.1
+ RegExp, // 21.2
+
+ // 22 Indexed Collections
+ Array, // 22.1
+
+ Int8Array,
+ Uint8Array,
+ Uint8ClampedArray,
+ Int16Array,
+ Uint16Array,
+ Int32Array,
+ Uint32Array,
+ Float32Array,
+ Float64Array,
+ BigInt64Array,
+ BigUint64Array,
+
+ // 23 Keyed Collections
+ Map, // 23.1
+ Set, // 23.2
+ WeakMap, // 23.3
+ WeakSet, // 23.4
+
+ // 24 Structured Data
+ ArrayBuffer, // 24.1
+ DataView, // 24.3
+ JSON, // 24.5
+ Promise, // 25.4
+
+ // 26 Reflection
+ Reflect, // 26.1
+ Proxy, // 26.2
+
+ // B.2.1
+ escape,
+ unescape,
+
+ // Web compatibility
+ clearImmediate,
+ clearInterval,
+ clearTimeout,
+ decodeURI,
+ decodeURIComponent,
+ encodeURI,
+ encodeURIComponent,
+ setImmediate,
+ setInterval,
+ setTimeout,
+
+ // Other APIs
+ console,
+ BigInt,
+ Atomics,
+ WebAssembly,
+ SharedArrayBuffer
+ ];
+
+ if (typeof Intl !== 'undefined')
+ intrinsics.push(Intl);
+
+ intrinsics.forEach(deepFreeze);
+
+ function deepFreeze(root) {
+
+ const { freeze, getOwnPropertyDescriptors, getPrototypeOf } = Object;
+ const { ownKeys } = Reflect;
+
+ // Objects that are deeply frozen.
+ // It turns out that Error is reachable from WebAssembly so it is
+ // explicitly added here to ensure it is not frozen
+ const frozenSet = new WeakSet([Error, Error.prototype]);
+
+ /**
+ * "innerDeepFreeze()" acts like "Object.freeze()", except that:
+ *
+ * To deepFreeze an object is to freeze it and all objects transitively
+ * reachable from it via transitive reflective property and prototype
+ * traversal.
+ */
+ function innerDeepFreeze(node) {
+ // Objects that we have frozen in this round.
+ const freezingSet = new Set();
+
+ // If val is something we should be freezing but aren't yet,
+ // add it to freezingSet.
+ function enqueue(val) {
+ if (Object(val) !== val) {
+ // ignore primitives
+ return;
+ }
+ const type = typeof val;
+ if (type !== 'object' && type !== 'function') {
+ // NB: handle for any new cases in future
+ }
+ if (frozenSet.has(val) || freezingSet.has(val)) {
+ // todo use uncurried form
+ // Ignore if already frozen or freezing
+ return;
+ }
+ freezingSet.add(val); // todo use uncurried form
+ }
+
+ function doFreeze(obj) {
+ // Immediately freeze the object to ensure reactive
+ // objects such as proxies won't add properties
+ // during traversal, before they get frozen.
+
+ // Object are verified before being enqueued,
+ // therefore this is a valid candidate.
+ // Throws if this fails (strict mode).
+ freeze(obj);
+
+ // We rely upon certain commitments of Object.freeze and proxies here
+
+ // Get stable/immutable outbound links before a Proxy has a chance to do
+ // something sneaky.
+ const proto = getPrototypeOf(obj);
+ const descs = getOwnPropertyDescriptors(obj);
+ enqueue(proto);
+ ownKeys(descs).forEach((name) => {
+ // todo uncurried form
+ // todo: getOwnPropertyDescriptors is guaranteed to return well-formed
+ // descriptors, but they still inherit from Object.prototype. If
+ // someone has poisoned Object.prototype to add 'value' or 'get'
+ // properties, then a simple 'if ("value" in desc)' or 'desc.value'
+ // test could be confused. We use hasOwnProperty to be sure about
+ // whether 'value' is present or not, which tells us for sure that
+ // this is a data property.
+ const desc = descs[name];
+ if ('value' in desc) {
+ // todo uncurried form
+ enqueue(desc.value);
+ } else {
+ enqueue(desc.get);
+ enqueue(desc.set);
+ }
+ });
+ }
+
+ function dequeue() {
+ // New values added before forEach() has finished will be visited.
+ freezingSet.forEach(doFreeze); // todo curried forEach
+ }
+
+ function commit() {
+ // todo curried forEach
+ // we capture the real WeakSet.prototype.add above, in case someone
+ // changes it. The two-argument form of forEach passes the second
+ // argument as the 'this' binding, so we add to the correct set.
+ freezingSet.forEach(frozenSet.add, frozenSet);
+ }
+
+ enqueue(node);
+ dequeue();
+ commit();
+ }
+
+ innerDeepFreeze(root);
+ return root;
+ }
+};
diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js
index b6dbc47274..0dc1b61ef9 100644
--- a/lib/internal/main/worker_thread.js
+++ b/lib/internal/main/worker_thread.js
@@ -6,6 +6,7 @@
const {
initializeDeprecations,
initializeESMLoader,
+ initializeFrozenIntrinsics,
initializeReport,
loadPreloadModules,
setupTraceCategoryState
@@ -81,6 +82,7 @@ port.on('message', (message) => {
require('internal/process/policy').setup(manifestSrc, manifestURL);
}
initializeDeprecations();
+ initializeFrozenIntrinsics();
initializeESMLoader();
loadPreloadModules();
publicWorker.parentPort = publicPort;
diff --git a/node.gyp b/node.gyp
index 28cbf63366..a6e884e15c 100644
--- a/node.gyp
+++ b/node.gyp
@@ -123,6 +123,7 @@
'lib/internal/error-serdes.js',
'lib/internal/fixed_queue.js',
'lib/internal/freelist.js',
+ 'lib/internal/freeze_intrinsics.js',
'lib/internal/fs/promises.js',
'lib/internal/fs/read_file_context.js',
'lib/internal/fs/streams.js',
diff --git a/src/node_options.cc b/src/node_options.cc
index ebd4baf2b7..9f94c75767 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -187,6 +187,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
kAllowedInEnvironment);
#endif // NODE_REPORT
AddOption("--expose-internals", "", &EnvironmentOptions::expose_internals);
+ AddOption("--frozen-intrinsics",
+ "experimental frozen intrinsics support",
+ &EnvironmentOptions::frozen_intrinsics,
+ kAllowedInEnvironment);
AddOption("--http-parser",
"Select which HTTP parser to use; either 'legacy' or 'llhttp' "
#ifdef NODE_EXPERIMENTAL_HTTP_DEFAULT
diff --git a/src/node_options.h b/src/node_options.h
index 46963181aa..b3ac910b3d 100644
--- a/src/node_options.h
+++ b/src/node_options.h
@@ -100,6 +100,7 @@ class EnvironmentOptions : public Options {
bool experimental_repl_await = false;
bool experimental_vm_modules = false;
bool expose_internals = false;
+ bool frozen_intrinsics = false;
std::string http_parser =
#ifdef NODE_EXPERIMENTAL_HTTP_DEFAULT
"llhttp";
diff --git a/test/parallel/test-freeze-intrinsics.js b/test/parallel/test-freeze-intrinsics.js
new file mode 100644
index 0000000000..bb94946d2d
--- /dev/null
+++ b/test/parallel/test-freeze-intrinsics.js
@@ -0,0 +1,10 @@
+// Flags: --frozen-intrinsics
+'use strict';
+require('../common');
+const assert = require('assert');
+
+try {
+ Object.defineProperty = 'asdf';
+ assert(false);
+} catch {
+}
diff --git a/tools/license-builder.sh b/tools/license-builder.sh
index 1e82d9da0d..55f630285e 100755
--- a/tools/license-builder.sh
+++ b/tools/license-builder.sh
@@ -94,6 +94,9 @@ addlicense "node-inspect" "deps/node-inspect" "$(cat ${rootdir}/deps/node-inspec
# large_pages
addlicense "large_pages" "src/large_pages" "$(sed -e '/SPDX-License-Identifier/,$d' -e 's/^\/\///' ${rootdir}/src/large_pages/node_large_page.h)"
+# deep_freeze
+addlicense "caja" "lib/internal/freeze_intrinsics.js" "$(sed -e '/SPDX-License-Identifier/,$d' -e 's/^\/\///' ${rootdir}/lib/internal/freeze_intrinsics.js)"
+
# brotli
addlicense "brotli" "deps/brotli" "$(cat ${rootdir}/deps/brotli/LICENSE)"