Skip to content

Commit 3e54f90

Browse files
guybedfordBridgeAR
authored andcommitted
bootstrap: experimental --frozen-intrinsics flag
PR-URL: #25685 Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 1c8076e commit 3e54f90

11 files changed

+316
-0
lines changed

LICENSE

+18
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,24 @@ The externally maintained libraries used by Node.js are:
13601360
OR OTHER DEALINGS IN THE SOFTWARE.
13611361
"""
13621362

1363+
- caja, located at lib/internal/freeze_intrinsics.js, is licensed as follows:
1364+
"""
1365+
Adapted from SES/Caja - Copyright (C) 2011 Google Inc.
1366+
Copyright (C) 2018 Agoric
1367+
1368+
Licensed under the Apache License, Version 2.0 (the "License");
1369+
you may not use this file except in compliance with the License.
1370+
You may obtain a copy of the License at
1371+
1372+
http://www.apache.org/licenses/LICENSE-2.0
1373+
1374+
Unless required by applicable law or agreed to in writing, software
1375+
distributed under the License is distributed on an "AS IS" BASIS,
1376+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1377+
See the License for the specific language governing permissions and
1378+
limitations under the License.
1379+
"""
1380+
13631381
- brotli, located at deps/brotli, is licensed as follows:
13641382
"""
13651383
Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors.

doc/api/cli.md

+20
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,25 @@ added: v6.0.0
177177
Force FIPS-compliant crypto on startup. (Cannot be disabled from script code.)
178178
(Same requirements as `--enable-fips`.)
179179

180+
### `--frozen-intrinsics`
181+
<!-- YAML
182+
added: REPLACEME
183+
-->
184+
185+
> Stability: 1 - Experimental
186+
187+
Enable experimental frozen intrinsics like `Array` and `Object`.
188+
189+
Support is currently only provided for the root context and no guarantees are
190+
currently provided that `global.Array` is indeed the default intrinsic
191+
reference.
192+
193+
**Code breakage is highly likely with this flag**, especially since limited
194+
support for subclassing builtins is provided currently due to ECMA-262 bug
195+
https://github.com/tc39/ecma262/pull/1320.
196+
197+
Both of the above may change in future updates, which will be breaking changes.
198+
180199
### `--http-parser=library`
181200
<!-- YAML
182201
added: v11.4.0
@@ -671,6 +690,7 @@ Node.js options that are allowed are:
671690
- `--experimental-report`
672691
- `--experimental-vm-modules`
673692
- `--force-fips`
693+
- `--frozen-intrinsics`
674694
- `--icu-data-dir`
675695
- `--inspect`
676696
- `--inspect-brk`

doc/node.1

+3
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ Force FIPS-compliant crypto on startup
144144
Same requirements as
145145
.Fl -enable-fips .
146146
.
147+
.It Fl -frozen-intrinsics
148+
Enable experimental frozen intrinsics support.
149+
.
147150
.It Fl -http-parser Ns = Ns Ar library
148151
Chooses an HTTP parser library. Available values are
149152
.Sy llhttp

lib/internal/bootstrap/pre_execution.js

+10
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ function prepareMainThreadExecution() {
3131
initializeClusterIPC();
3232

3333
initializeDeprecations();
34+
initializeFrozenIntrinsics();
3435
initializeESMLoader();
3536
loadPreloadModules();
3637
}
@@ -230,6 +231,14 @@ function initializeESMLoader() {
230231
}
231232
}
232233

234+
function initializeFrozenIntrinsics() {
235+
if (getOptionValue('--frozen-intrinsics')) {
236+
process.emitWarning('The --frozen-intrinsics flag is experimental',
237+
'ExperimentalWarning');
238+
require('internal/freeze_intrinsics')();
239+
}
240+
}
241+
233242
function loadPreloadModules() {
234243
// For user code, we preload modules if `-r` is passed
235244
const preloadModules = getOptionValue('--require');
@@ -245,6 +254,7 @@ module.exports = {
245254
prepareMainThreadExecution,
246255
initializeDeprecations,
247256
initializeESMLoader,
257+
initializeFrozenIntrinsics,
248258
loadPreloadModules,
249259
setupTraceCategoryState,
250260
initializeReport

lib/internal/freeze_intrinsics.js

+244
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
// Adapted from SES/Caja - Copyright (C) 2011 Google Inc.
2+
// Copyright (C) 2018 Agoric
3+
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// SPDX-License-Identifier: MIT
16+
17+
// based upon:
18+
// https://github.com/google/caja/blob/master/src/com/google/caja/ses/startSES.js
19+
// https://github.com/google/caja/blob/master/src/com/google/caja/ses/repairES5.js
20+
// https://github.com/tc39/proposal-frozen-realms/blob/91ac390e3451da92b5c27e354b39e52b7636a437/shim/src/deep-freeze.js
21+
22+
/* global WebAssembly, SharedArrayBuffer, console */
23+
'use strict';
24+
module.exports = function() {
25+
26+
const intrinsics = [
27+
// Anonymous Intrinsics
28+
// ThrowTypeError
29+
Object.getOwnPropertyDescriptor(Function.prototype, 'caller').get,
30+
// IteratorPrototype
31+
Object.getPrototypeOf(
32+
Object.getPrototypeOf(new Array()[Symbol.iterator]())
33+
),
34+
// ArrayIteratorPrototype
35+
Object.getPrototypeOf(new Array()[Symbol.iterator]()),
36+
// StringIteratorPrototype
37+
Object.getPrototypeOf(new String()[Symbol.iterator]()),
38+
// MapIteratorPrototype
39+
Object.getPrototypeOf(new Map()[Symbol.iterator]()),
40+
// SetIteratorPrototype
41+
Object.getPrototypeOf(new Set()[Symbol.iterator]()),
42+
// GeneratorFunction
43+
Object.getPrototypeOf(function* () {}),
44+
// AsyncFunction
45+
Object.getPrototypeOf(async function() {}),
46+
// AsyncGeneratorFunction
47+
Object.getPrototypeOf(async function* () {}),
48+
// TypedArray
49+
Object.getPrototypeOf(Uint8Array),
50+
51+
// 18 The Global Object
52+
eval,
53+
isFinite,
54+
isNaN,
55+
parseFloat,
56+
parseInt,
57+
decodeURI,
58+
decodeURIComponent,
59+
encodeURI,
60+
encodeURIComponent,
61+
62+
// 19 Fundamental Objects
63+
Object, // 19.1
64+
Function, // 19.2
65+
Boolean, // 19.3
66+
Symbol, // 19.4
67+
68+
// Disabled pending stack trace mutation handling
69+
// Error, // 19.5
70+
// EvalError,
71+
// RangeError,
72+
// ReferenceError,
73+
// SyntaxError,
74+
// TypeError,
75+
// URIError,
76+
77+
// 20 Numbers and Dates
78+
Number, // 20.1
79+
Math, // 20.2
80+
Date, // 20.3
81+
82+
// 21 Text Processing
83+
String, // 21.1
84+
RegExp, // 21.2
85+
86+
// 22 Indexed Collections
87+
Array, // 22.1
88+
89+
Int8Array,
90+
Uint8Array,
91+
Uint8ClampedArray,
92+
Int16Array,
93+
Uint16Array,
94+
Int32Array,
95+
Uint32Array,
96+
Float32Array,
97+
Float64Array,
98+
BigInt64Array,
99+
BigUint64Array,
100+
101+
// 23 Keyed Collections
102+
Map, // 23.1
103+
Set, // 23.2
104+
WeakMap, // 23.3
105+
WeakSet, // 23.4
106+
107+
// 24 Structured Data
108+
ArrayBuffer, // 24.1
109+
DataView, // 24.3
110+
JSON, // 24.5
111+
Promise, // 25.4
112+
113+
// 26 Reflection
114+
Reflect, // 26.1
115+
Proxy, // 26.2
116+
117+
// B.2.1
118+
escape,
119+
unescape,
120+
121+
// Web compatibility
122+
clearImmediate,
123+
clearInterval,
124+
clearTimeout,
125+
decodeURI,
126+
decodeURIComponent,
127+
encodeURI,
128+
encodeURIComponent,
129+
setImmediate,
130+
setInterval,
131+
setTimeout,
132+
133+
// Other APIs
134+
console,
135+
BigInt,
136+
Atomics,
137+
WebAssembly,
138+
SharedArrayBuffer
139+
];
140+
141+
if (typeof Intl !== 'undefined')
142+
intrinsics.push(Intl);
143+
144+
intrinsics.forEach(deepFreeze);
145+
146+
function deepFreeze(root) {
147+
148+
const { freeze, getOwnPropertyDescriptors, getPrototypeOf } = Object;
149+
const { ownKeys } = Reflect;
150+
151+
// Objects that are deeply frozen.
152+
// It turns out that Error is reachable from WebAssembly so it is
153+
// explicitly added here to ensure it is not frozen
154+
const frozenSet = new WeakSet([Error, Error.prototype]);
155+
156+
/**
157+
* "innerDeepFreeze()" acts like "Object.freeze()", except that:
158+
*
159+
* To deepFreeze an object is to freeze it and all objects transitively
160+
* reachable from it via transitive reflective property and prototype
161+
* traversal.
162+
*/
163+
function innerDeepFreeze(node) {
164+
// Objects that we have frozen in this round.
165+
const freezingSet = new Set();
166+
167+
// If val is something we should be freezing but aren't yet,
168+
// add it to freezingSet.
169+
function enqueue(val) {
170+
if (Object(val) !== val) {
171+
// ignore primitives
172+
return;
173+
}
174+
const type = typeof val;
175+
if (type !== 'object' && type !== 'function') {
176+
// NB: handle for any new cases in future
177+
}
178+
if (frozenSet.has(val) || freezingSet.has(val)) {
179+
// todo use uncurried form
180+
// Ignore if already frozen or freezing
181+
return;
182+
}
183+
freezingSet.add(val); // todo use uncurried form
184+
}
185+
186+
function doFreeze(obj) {
187+
// Immediately freeze the object to ensure reactive
188+
// objects such as proxies won't add properties
189+
// during traversal, before they get frozen.
190+
191+
// Object are verified before being enqueued,
192+
// therefore this is a valid candidate.
193+
// Throws if this fails (strict mode).
194+
freeze(obj);
195+
196+
// We rely upon certain commitments of Object.freeze and proxies here
197+
198+
// Get stable/immutable outbound links before a Proxy has a chance to do
199+
// something sneaky.
200+
const proto = getPrototypeOf(obj);
201+
const descs = getOwnPropertyDescriptors(obj);
202+
enqueue(proto);
203+
ownKeys(descs).forEach((name) => {
204+
// todo uncurried form
205+
// todo: getOwnPropertyDescriptors is guaranteed to return well-formed
206+
// descriptors, but they still inherit from Object.prototype. If
207+
// someone has poisoned Object.prototype to add 'value' or 'get'
208+
// properties, then a simple 'if ("value" in desc)' or 'desc.value'
209+
// test could be confused. We use hasOwnProperty to be sure about
210+
// whether 'value' is present or not, which tells us for sure that
211+
// this is a data property.
212+
const desc = descs[name];
213+
if ('value' in desc) {
214+
// todo uncurried form
215+
enqueue(desc.value);
216+
} else {
217+
enqueue(desc.get);
218+
enqueue(desc.set);
219+
}
220+
});
221+
}
222+
223+
function dequeue() {
224+
// New values added before forEach() has finished will be visited.
225+
freezingSet.forEach(doFreeze); // todo curried forEach
226+
}
227+
228+
function commit() {
229+
// todo curried forEach
230+
// we capture the real WeakSet.prototype.add above, in case someone
231+
// changes it. The two-argument form of forEach passes the second
232+
// argument as the 'this' binding, so we add to the correct set.
233+
freezingSet.forEach(frozenSet.add, frozenSet);
234+
}
235+
236+
enqueue(node);
237+
dequeue();
238+
commit();
239+
}
240+
241+
innerDeepFreeze(root);
242+
return root;
243+
}
244+
};

lib/internal/main/worker_thread.js

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
const {
77
initializeDeprecations,
88
initializeESMLoader,
9+
initializeFrozenIntrinsics,
910
initializeReport,
1011
loadPreloadModules,
1112
setupTraceCategoryState
@@ -81,6 +82,7 @@ port.on('message', (message) => {
8182
require('internal/process/policy').setup(manifestSrc, manifestURL);
8283
}
8384
initializeDeprecations();
85+
initializeFrozenIntrinsics();
8486
initializeESMLoader();
8587
loadPreloadModules();
8688
publicWorker.parentPort = publicPort;

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
'lib/internal/error-serdes.js',
124124
'lib/internal/fixed_queue.js',
125125
'lib/internal/freelist.js',
126+
'lib/internal/freeze_intrinsics.js',
126127
'lib/internal/fs/promises.js',
127128
'lib/internal/fs/read_file_context.js',
128129
'lib/internal/fs/streams.js',

src/node_options.cc

+4
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
187187
kAllowedInEnvironment);
188188
#endif // NODE_REPORT
189189
AddOption("--expose-internals", "", &EnvironmentOptions::expose_internals);
190+
AddOption("--frozen-intrinsics",
191+
"experimental frozen intrinsics support",
192+
&EnvironmentOptions::frozen_intrinsics,
193+
kAllowedInEnvironment);
190194
AddOption("--http-parser",
191195
"Select which HTTP parser to use; either 'legacy' or 'llhttp' "
192196
#ifdef NODE_EXPERIMENTAL_HTTP_DEFAULT

src/node_options.h

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ class EnvironmentOptions : public Options {
100100
bool experimental_repl_await = false;
101101
bool experimental_vm_modules = false;
102102
bool expose_internals = false;
103+
bool frozen_intrinsics = false;
103104
std::string http_parser =
104105
#ifdef NODE_EXPERIMENTAL_HTTP_DEFAULT
105106
"llhttp";
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Flags: --frozen-intrinsics
2+
'use strict';
3+
require('../common');
4+
const assert = require('assert');
5+
6+
try {
7+
Object.defineProperty = 'asdf';
8+
assert(false);
9+
} catch {
10+
}

0 commit comments

Comments
 (0)