dsh-ptc-plus 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +6 -10
- package/README.md +6 -10
- package/client.js +262 -31
- package/compiler-core.cjs +132 -130
- package/compiler-core.cjs.map +3 -3
- package/docs/README.md +1 -0
- package/docs/adr/0023-global-user-bindings.md +6 -6
- package/docs/adr/0025-use-versioned-logical-binding-identities.md +3 -3
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +5 -5
- package/docs/architecture.md +9 -5
- package/docs/client-ui.md +1 -1
- package/docs/durability-design.md +6 -3
- package/docs/installation.md +4 -4
- package/docs/runtime-reference.md +5 -1
- package/docs/stateful-binding-updates-proposal.md +8 -4
- package/docs/user-bindings.en.md +5 -3
- package/docs/user-bindings.md +4 -2
- package/docs/verification-optimization-work.md +41 -0
- package/docs/verification.md +15 -1
- package/internal/async-local-storage-intrinsics.js +38 -0
- package/internal/binding-declaration-contract.js +8 -0
- package/internal/binding-source-analysis.js +381 -64
- package/internal/binding-update-policy.js +7 -1
- package/internal/cell-analysis.js +21 -8
- package/internal/compiler-intrinsics.js +9 -43
- package/internal/compiler-module-links.js +7 -4
- package/internal/dynamic-environment-integration.js +8 -4
- package/internal/dynamic-environment-runtime.js +24 -3
- package/internal/dynamic-native-calls.js +4 -1
- package/internal/dynamic-native-runtime.js +15 -1
- package/internal/failure-reporting.js +38 -30
- package/internal/isolated-worker.js +17 -10
- package/internal/kernel-worker.js +493 -331
- package/internal/module-compilation.js +5 -4
- package/internal/module-transform-contract.js +32 -2
- package/internal/record-utils.js +24 -15
- package/internal/repl-value-observer.js +6 -3
- package/internal/runtime-intrinsics.js +101 -0
- package/internal/session-cell-executor.js +2 -1
- package/internal/session-journal-schema.js +30 -13
- package/internal/session-journal.js +19 -4
- package/internal/session-runtime.js +1 -0
- package/internal/source-position-map.js +19 -0
- package/internal/stateful-root-compiler.js +3 -1
- package/internal/typescript-transform.js +4 -2
- package/internal/user-binding-console-worker.js +101 -41
- package/internal/user-bindings.js +10 -2
- package/internal/value-wire.js +101 -71
- package/internal/worker-cwd-virtualization.js +80 -53
- package/internal/worker-environment.js +20 -0
- package/internal/worker-repl-realm.js +134 -0
- package/package.json +2 -1
- package/src/client-activity.js +8 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LIVE_MODULE_SEMANTICS } from './repl-rewrite-contract.js'
|
|
2
2
|
import { LEGACY_LANGUAGE_SEMANTICS, STATEFUL_LANGUAGE_SEMANTICS,
|
|
3
3
|
PROTECTED_LANGUAGE_SEMANTICS, normalizeLanguageSemantics } from './language-semantics.js'
|
|
4
|
+
import { moduleTransformForLanguage, normalizeModuleTransform } from './module-transform-contract.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Translate the public compatibility settings into the execution policy used
|
|
@@ -15,8 +16,10 @@ export function executionPolicies(config, replayRecord = undefined) {
|
|
|
15
16
|
const bindingPolicy = Object.freeze({ ...replayRecord.bindingPolicy })
|
|
16
17
|
const rewritesEnabled = Object.freeze({ ...replayRecord.rewritePolicy })
|
|
17
18
|
const moduleSemantics = Object.freeze({ ...replayRecord.moduleSemantics })
|
|
19
|
+
const languageSemantics = normalizeLanguageSemantics(replayRecord.languageSemantics ?? LEGACY_LANGUAGE_SEMANTICS)
|
|
18
20
|
return Object.freeze({
|
|
19
|
-
languageSemantics
|
|
21
|
+
languageSemantics,
|
|
22
|
+
moduleTransform: normalizeModuleTransform(replayRecord.moduleTransform, languageSemantics),
|
|
20
23
|
bindingPolicy,
|
|
21
24
|
rewritesEnabled,
|
|
22
25
|
moduleSemantics,
|
|
@@ -25,6 +28,7 @@ export function executionPolicies(config, replayRecord = undefined) {
|
|
|
25
28
|
if (config.bindingUpdates === 'stateful' && config.legacyBindingSettings !== true) {
|
|
26
29
|
return Object.freeze({
|
|
27
30
|
languageSemantics: STATEFUL_LANGUAGE_SEMANTICS,
|
|
31
|
+
moduleTransform: moduleTransformForLanguage(STATEFUL_LANGUAGE_SEMANTICS),
|
|
28
32
|
bindingPolicy: Object.freeze({ variableRedeclarations: true, functionClassRedeclarations: true }),
|
|
29
33
|
rewritesEnabled: Object.freeze({ autoRewriteImports: true, autoStripExports: true, autoSplitRedeclarations: true }),
|
|
30
34
|
moduleSemantics: LIVE_MODULE_SEMANTICS,
|
|
@@ -33,6 +37,7 @@ export function executionPolicies(config, replayRecord = undefined) {
|
|
|
33
37
|
if (config.bindingUpdates === 'protected' && config.legacyBindingSettings !== true) {
|
|
34
38
|
return Object.freeze({
|
|
35
39
|
languageSemantics: PROTECTED_LANGUAGE_SEMANTICS,
|
|
40
|
+
moduleTransform: moduleTransformForLanguage(PROTECTED_LANGUAGE_SEMANTICS),
|
|
36
41
|
bindingPolicy: Object.freeze({ variableRedeclarations: false, functionClassRedeclarations: false }),
|
|
37
42
|
// Module syntax is part of the language in protected mode too. The
|
|
38
43
|
// policy protects existing names; it does not remove valid syntax.
|
|
@@ -42,6 +47,7 @@ export function executionPolicies(config, replayRecord = undefined) {
|
|
|
42
47
|
}
|
|
43
48
|
return Object.freeze({
|
|
44
49
|
languageSemantics: LEGACY_LANGUAGE_SEMANTICS,
|
|
50
|
+
moduleTransform: moduleTransformForLanguage(LEGACY_LANGUAGE_SEMANTICS),
|
|
45
51
|
bindingPolicy: Object.freeze({
|
|
46
52
|
variableRedeclarations: config.looseTopLevelRedeclarations === true,
|
|
47
53
|
functionClassRedeclarations: config.looseTopLevelFunctionClassRedeclarations === true,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
renderDurabilityReason,
|
|
18
18
|
renderDurabilityReasons,
|
|
19
19
|
} from './module-policy.js'
|
|
20
|
-
import { applySourceEdits, mapSourcePosition, mapSourceSpan } from './source-position-map.js'
|
|
20
|
+
import { applySourceEdits, mapSourcePosition, mapSourceSpan, sourceRangeHasOriginalText } from './source-position-map.js'
|
|
21
21
|
import { SKIP_AST_CHILDREN, walkAst } from './ast-traversal.js'
|
|
22
22
|
import { compileStatefulRoot } from './stateful-root-compiler.js'
|
|
23
23
|
import { CELL_PARSER_PLUGINS, bindCompilerIntrinsics, lowerStatefulDecorators, lowerStatefulResources, lowerNativeLanguageSource, normalizeStatefulScopes } from './repl-scope-normalizer.js'
|
|
@@ -240,7 +240,9 @@ function staticModuleClassification(moduleLoads) {
|
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
/** Conservatively classify a cell before giving it non-journalable capability. */
|
|
243
|
-
export function classifyDurability(code, knownBindings = new Set(), {
|
|
243
|
+
export function classifyDurability(code, knownBindings = new Set(), {
|
|
244
|
+
sourceType = 'script', internalModules = new Set(), sourceMap = undefined, originalSource = undefined,
|
|
245
|
+
} = {}) {
|
|
244
246
|
let body
|
|
245
247
|
let moduleLoads = []
|
|
246
248
|
if (sourceType === 'module') {
|
|
@@ -297,11 +299,12 @@ export function classifyDurability(code, knownBindings = new Set(), { sourceType
|
|
|
297
299
|
}
|
|
298
300
|
return false
|
|
299
301
|
}
|
|
300
|
-
walkAst(body, (node, parent, parentKey, { scopes, strict }) => {
|
|
302
|
+
walkAst(body, (node, parent, parentKey, { scopes, strict, thisIsGlobal }) => {
|
|
301
303
|
let nestedScopes = scopes
|
|
302
304
|
if (isFunction(node)) {
|
|
303
305
|
strict ||= hasStrictDirective(node.body)
|
|
304
306
|
nestedScopes = [...scopes, functionParameterBindings(node)]
|
|
307
|
+
if (node.type !== 'ArrowFunctionExpression') thisIsGlobal = false
|
|
305
308
|
} else if (node.type === 'BlockStatement' && node !== body) {
|
|
306
309
|
const names = directBlockBindings(node.body)
|
|
307
310
|
if (isFunction(parent)) addFunctionVariables(node, names, strict)
|
|
@@ -326,6 +329,11 @@ export function classifyDurability(code, knownBindings = new Set(), { sourceType
|
|
|
326
329
|
if (node.type === 'ImportExpression') {
|
|
327
330
|
classifyModule(node.source)
|
|
328
331
|
}
|
|
332
|
+
const sourceOwnedThis = sourceMap === undefined
|
|
333
|
+
|| sourceRangeHasOriginalText(sourceMap, code, originalSource, node.start, node.end)
|
|
334
|
+
if (node.type === 'ThisExpression' && thisIsGlobal && sourceOwnedThis) {
|
|
335
|
+
addReason(Object.freeze({ kind: 'ambient', name: 'globalThis' }))
|
|
336
|
+
}
|
|
329
337
|
if (node.type === 'CallExpression' && node.callee?.type === 'Identifier' && node.callee.name === 'require'
|
|
330
338
|
&& !isBound('require', nestedScopes)) {
|
|
331
339
|
classifyModule(node.arguments[0])
|
|
@@ -367,8 +375,8 @@ export function classifyDurability(code, knownBindings = new Set(), { sourceType
|
|
|
367
375
|
addReason(Object.freeze({ kind: 'ambient', name: 'Math' }))
|
|
368
376
|
}
|
|
369
377
|
}
|
|
370
|
-
return { scopes: nestedScopes, strict }
|
|
371
|
-
}, undefined, { scopes: [rootBindings], strict: rootStrict })
|
|
378
|
+
return { scopes: nestedScopes, strict, thisIsGlobal }
|
|
379
|
+
}, undefined, { scopes: [rootBindings], strict: rootStrict, thisIsGlobal: sourceType === 'script' })
|
|
372
380
|
return {
|
|
373
381
|
durability: reasons.size === 0 ? 'durable' : 'volatile',
|
|
374
382
|
reasons: Object.freeze([...reasons.values()]),
|
|
@@ -473,12 +481,17 @@ export function prepareProgram(program, options = {}) {
|
|
|
473
481
|
prepared = { ...prepared, deferredHelpers: normalized.deferredHelpers }
|
|
474
482
|
intrinsicContext.expression = `this[${JSON.stringify(prepared.rootRuntimeName)}].intrinsics`
|
|
475
483
|
const classificationSource = bindCompilerIntrinsics(lowerStatefulDecorators(
|
|
476
|
-
{ ...prepared, code: prepared.classificationCode
|
|
484
|
+
{ ...prepared, code: prepared.classificationCode, sourceMap: prepared.classificationSourceMap },
|
|
485
|
+
undefined, intrinsicContext), intrinsicContext)
|
|
477
486
|
prepared = bindCompilerIntrinsics(lowerStatefulDecorators(prepared, undefined, intrinsicContext), intrinsicContext)
|
|
478
487
|
prepared = lowerStatefulResources(prepared, { intrinsicContext, nativeUsing: options.nativeUsing })
|
|
479
488
|
const parsed = parseExecutableCell(prepared.code, { eraseTypes: true })
|
|
480
|
-
const
|
|
481
|
-
|
|
489
|
+
const classificationCode = parseExecutableCell(classificationSource.code, { eraseTypes: true }).code
|
|
490
|
+
const classification = classifyDurability(classificationCode,
|
|
491
|
+
new Set([...(options.knownBindings ?? []), ...prepared.declared]), {
|
|
492
|
+
sourceMap: classificationSource.sourceMap,
|
|
493
|
+
originalSource: program,
|
|
494
|
+
})
|
|
482
495
|
const reasons = [...staticModuleClassification(prepared.moduleLoads), ...classification.reasons]
|
|
483
496
|
const lowered = rewriteCellReturns(parsed.code, prepared.sourceMap,
|
|
484
497
|
new Set([...prepared.declared, ...(options.knownBindings ?? [])]))
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { stringifyCompilerData } from './compiler-data.js'
|
|
2
2
|
import { compilerStorageSource } from './compiler-storage-source.js'
|
|
3
3
|
import { compilerDescriptorSource } from './compiler-descriptors.js'
|
|
4
|
+
import { runtimeIntrinsics } from './runtime-intrinsics.js'
|
|
4
5
|
|
|
5
6
|
/** Compiler operations are captured before an owned realm executes user code. */
|
|
6
7
|
const defaultRealmFunction = Function
|
|
7
8
|
const realms = new WeakMap()
|
|
8
|
-
const
|
|
9
|
-
const weakMapGet = uncurry(WeakMap.prototype.get)
|
|
10
|
-
const weakMapSet = uncurry(WeakMap.prototype.set)
|
|
11
|
-
const mapKeys = uncurry(Map.prototype.keys)
|
|
12
|
-
const mapIteratorNext = uncurry(Object.getPrototypeOf(new Map().keys()).next)
|
|
9
|
+
const { weakMapGet, weakMapSet } = runtimeIntrinsics
|
|
13
10
|
|
|
14
11
|
export function captureCompilerIntrinsics(realmFunction = defaultRealmFunction) {
|
|
15
12
|
let intrinsics = weakMapGet(realms, realmFunction)
|
|
@@ -21,7 +18,8 @@ export function captureCompilerIntrinsics(realmFunction = defaultRealmFunction)
|
|
|
21
18
|
const object=properties.Object;
|
|
22
19
|
const symbol=Symbol;
|
|
23
20
|
return {Object:object,WeakMap,WeakSet,unscopables:symbol.unscopables,Reflect:{apply:nativeReflect.apply,
|
|
24
|
-
construct:nativeReflect.construct,
|
|
21
|
+
construct:nativeReflect.construct,deleteProperty:nativeReflect.deleteProperty,has:nativeReflect.has,
|
|
22
|
+
ownKeys:nativeReflect.ownKeys,defineProperty:properties.reflectDefineProperty,
|
|
25
23
|
get decorate(){return nativeReflect.decorate}},TypeError,ReferenceError,SyntaxError,Error,Symbol,String,Number,
|
|
26
24
|
Promise:{resolve:Promise.resolve.bind(Promise),reject:Promise.reject.bind(Promise)},
|
|
27
25
|
SuppressedError:typeof SuppressedError==='function'?SuppressedError:undefined,
|
|
@@ -36,49 +34,17 @@ export const moduleCompilerIntrinsics = captureCompilerIntrinsics()
|
|
|
36
34
|
/** Internal graph records use captured collection operations. Source-owned
|
|
37
35
|
* objects and callbacks keep their original realm and observable methods. */
|
|
38
36
|
export const moduleRuntimeIntrinsics = {
|
|
37
|
+
...runtimeIntrinsics,
|
|
39
38
|
Object: moduleCompilerIntrinsics.Object,
|
|
40
39
|
Reflect: moduleCompilerIntrinsics.Reflect,
|
|
41
40
|
Error: moduleCompilerIntrinsics.Error,
|
|
42
|
-
|
|
43
|
-
mapGet: uncurry(Map.prototype.get),
|
|
44
|
-
mapHas: uncurry(Map.prototype.has),
|
|
45
|
-
mapSet: uncurry(Map.prototype.set),
|
|
46
|
-
mapDelete: uncurry(Map.prototype.delete),
|
|
47
|
-
mapClear: uncurry(Map.prototype.clear),
|
|
48
|
-
mapSize: uncurry(Object.getOwnPropertyDescriptor(Map.prototype, 'size').get),
|
|
49
|
-
mapFirstKey: value => mapIteratorNext(mapKeys(value)).value,
|
|
50
|
-
mapForEach: uncurry(Map.prototype.forEach),
|
|
51
|
-
setHas: uncurry(Set.prototype.has),
|
|
52
|
-
setAdd: uncurry(Set.prototype.add),
|
|
53
|
-
setDelete: uncurry(Set.prototype.delete),
|
|
54
|
-
setClear: uncurry(Set.prototype.clear),
|
|
55
|
-
setForEach: uncurry(Set.prototype.forEach),
|
|
56
|
-
weakMapHas: uncurry(WeakMap.prototype.has),
|
|
57
|
-
weakMapGet, weakMapSet,
|
|
58
|
-
isArray: Array.isArray,
|
|
59
|
-
includes: uncurry(Array.prototype.includes),
|
|
60
|
-
join: uncurry(Array.prototype.join),
|
|
61
|
-
sliceString: uncurry(String.prototype.slice),
|
|
62
|
-
replaceAllString: uncurry(String.prototype.replaceAll),
|
|
63
|
-
includesString: uncurry(String.prototype.includes),
|
|
64
|
-
floor: Math.floor,
|
|
65
|
-
min: Math.min,
|
|
66
|
-
max: Math.max,
|
|
41
|
+
TypeError: moduleCompilerIntrinsics.TypeError,
|
|
67
42
|
// Only private native Promises enter this boundary. Source promises and the
|
|
68
43
|
// public result keep their prototypes; private reactions cannot select a
|
|
69
44
|
// source constructor/species or dynamically dispatch through then/catch.
|
|
70
45
|
observeOwnedPromise: moduleCompilerIntrinsics.observeOwnedPromise,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return target
|
|
75
|
-
},
|
|
76
|
-
copyArray(source, start = 0, end = source.length, target = []) {
|
|
77
|
-
for (let index = start; index < end; index++) this.appendArray(target, source[index])
|
|
78
|
-
return target
|
|
79
|
-
},
|
|
80
|
-
sort: uncurry(Array.prototype.sort),
|
|
81
|
-
compare: uncurry(String.prototype.localeCompare),
|
|
82
|
-
startsWith: uncurry(String.prototype.startsWith),
|
|
46
|
+
promiseThen: moduleCompilerIntrinsics.promiseThen,
|
|
47
|
+
promiseResolve: moduleCompilerIntrinsics.Promise.resolve,
|
|
48
|
+
promiseReject: moduleCompilerIntrinsics.Promise.reject,
|
|
83
49
|
stringify: stringifyCompilerData,
|
|
84
50
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url'
|
|
2
2
|
|
|
3
3
|
const encodeReference = encodeURIComponent
|
|
4
|
+
const objectEntries = Object.entries
|
|
5
|
+
const sortArray = Function.prototype.call.bind(Array.prototype.sort)
|
|
6
|
+
const jsonStringify = JSON.stringify
|
|
4
7
|
|
|
5
8
|
// These ambient inputs must remain outside source-owned CommonJS bindings.
|
|
6
9
|
export const commonJsCompilerGlobals = new Set(['process'])
|
|
@@ -21,14 +24,14 @@ const ATTRIBUTED_LINK_KIND = 'static-attributed'
|
|
|
21
24
|
* attribute-free links keep the reference they had before attributes were
|
|
22
25
|
* distinguished. */
|
|
23
26
|
export function staticModuleLinkReference(source, attributes) {
|
|
24
|
-
const entries =
|
|
27
|
+
const entries = objectEntries(attributes ?? {})
|
|
25
28
|
if (entries.length === 0) return compilerModuleReference(STATIC_LINK_KIND, source)
|
|
26
|
-
entries
|
|
27
|
-
return compilerModuleReference(ATTRIBUTED_LINK_KIND,
|
|
29
|
+
sortArray(entries, (left, right) => left[0] < right[0] ? -1 : left[0] > right[0] ? 1 : 0)
|
|
30
|
+
return compilerModuleReference(ATTRIBUTED_LINK_KIND, jsonStringify([source, entries]))
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
/** Compiler helpers resolve from their own URL, independently of source-owned
|
|
31
34
|
* wrapper bindings and the ESM translator's restricted CommonJS require. */
|
|
32
35
|
export function commonJsCompilerImport(url) {
|
|
33
|
-
return `process.getBuiltinModule('module').createRequire(${
|
|
36
|
+
return `process.getBuiltinModule('module').createRequire(${jsonStringify(url.href)})(${jsonStringify(fileURLToPath(url))})`
|
|
34
37
|
}
|
|
@@ -34,15 +34,19 @@ export function adaptDynamicCell(code, sourceMap, options) {
|
|
|
34
34
|
let setup
|
|
35
35
|
if (options.module === true) {
|
|
36
36
|
const runtimeUrl = new URL('./dynamic-environment-runtime.js', import.meta.url)
|
|
37
|
+
const interfaceOwner = allocate('dynamic_interface_owner')
|
|
38
|
+
const runtimeOptions = `{interfaceOwner:${interfaceOwner}${options.importOperation === undefined
|
|
39
|
+
? '' : `,importModule:${options.importOperation}`}}`
|
|
37
40
|
if (options.sourceType === 'module') {
|
|
38
41
|
const factory = allocate('dynamic_factory')
|
|
39
|
-
setup = `\nimport {createDynamicEnvironmentRuntime as ${factory}} from ${JSON.stringify(runtimeUrl.href)};\n`
|
|
40
|
-
expression = `${factory}(${
|
|
41
|
-
options = { ...options, internalBindings: new Set([...options.internalBindings ?? [], factory]) }
|
|
42
|
+
setup = `\nimport {createDynamicEnvironmentRuntime as ${factory}} from ${JSON.stringify(runtimeUrl.href)};\nfunction ${interfaceOwner}(){}\n`
|
|
43
|
+
expression = `${factory}(${runtimeOptions}).installIntrinsics().environment({strict:true,getThis:()=>void 0})`
|
|
44
|
+
options = { ...options, internalBindings: new Set([...options.internalBindings ?? [], factory, interfaceOwner]) }
|
|
42
45
|
} else {
|
|
43
46
|
const names = ['exports', 'module', 'require', '__filename', '__dirname', 'arguments']
|
|
44
47
|
const bindings = names.map(name => `[${JSON.stringify(name)},{kind:"param",get:()=>${name}${name === 'arguments' && strict ? '' : `,set:v=>${name}=v`}}]`).join(',')
|
|
45
|
-
setup = `\nconst ${environmentName}=${commonJsCompilerImport(runtimeUrl)}.createDynamicEnvironmentRuntime(${
|
|
48
|
+
setup = `\nfunction ${interfaceOwner}(){}\nconst ${environmentName}=${commonJsCompilerImport(runtimeUrl)}.createDynamicEnvironmentRuntime(${runtimeOptions}).installIntrinsics().environment({strict:${strict},getThis:()=>this,getNewTarget:()=>new.target,allowNewTarget:true,nativeBindings:[${bindings}]});\n`
|
|
49
|
+
options = { ...options, internalBindings: new Set([...options.internalBindings ?? [], interfaceOwner]) }
|
|
46
50
|
}
|
|
47
51
|
} else setup = options.nativeRoot === true ? `\nconst ${environmentName}=${options.environmentGlobal};\n`
|
|
48
52
|
: `\nconst ${environmentName}=${options.rootRuntime}.dynamic();\n`
|
|
@@ -16,6 +16,17 @@ function captureReflection(reflect) {
|
|
|
16
16
|
return captured
|
|
17
17
|
}
|
|
18
18
|
const defaultIntrinsics = { intrinsicEval: eval, realmFunction: Function, globalObject: globalThis, reflect: captureReflection(Reflect) }
|
|
19
|
+
let programAmbientResolver
|
|
20
|
+
|
|
21
|
+
/** Install the worker-owned resolver used by explicitly adapted program modules. */
|
|
22
|
+
export function installProgramAmbientResolver(resolve) {
|
|
23
|
+
if (programAmbientResolver !== undefined) throw new Error('program ambient resolver is already installed')
|
|
24
|
+
if (typeof resolve !== 'function') throw new TypeError('program ambient resolver must be a function')
|
|
25
|
+
programAmbientResolver = resolve
|
|
26
|
+
return () => {
|
|
27
|
+
if (programAmbientResolver === resolve) programAmbientResolver = undefined
|
|
28
|
+
}
|
|
29
|
+
}
|
|
19
30
|
|
|
20
31
|
const isObject = value => value !== null && (typeof value === 'object' || typeof value === 'function')
|
|
21
32
|
const isMap = types.isMap
|
|
@@ -45,7 +56,7 @@ const asBindings = value => {
|
|
|
45
56
|
export function createDynamicEnvironmentRuntime({
|
|
46
57
|
importModule = (source, options) => import(source, options),
|
|
47
58
|
intrinsicEval = defaultIntrinsics.intrinsicEval, realmFunction = defaultIntrinsics.realmFunction, globalObject = defaultIntrinsics.globalObject,
|
|
48
|
-
errors, reflect = defaultIntrinsics.reflect, object, interfaceOwner,
|
|
59
|
+
errors, reflect = defaultIntrinsics.reflect, object, interfaceOwner, resolveAmbient = programAmbientResolver,
|
|
49
60
|
} = {}) {
|
|
50
61
|
const intrinsics = captureCompilerIntrinsics(realmFunction)
|
|
51
62
|
const { unscopables, Object: intrinsicObject, array: internalArray } = intrinsics
|
|
@@ -143,7 +154,10 @@ export function createDynamicEnvironmentRuntime({
|
|
|
143
154
|
return deleted
|
|
144
155
|
},
|
|
145
156
|
}, object, strict)
|
|
146
|
-
const ambientReference = (name, strict) =>
|
|
157
|
+
const ambientReference = (name, strict) => {
|
|
158
|
+
const resolved = resolveAmbient?.(name)
|
|
159
|
+
if (resolved !== undefined) return reference(name, resolved, undefined, strict)
|
|
160
|
+
return reference(name, {
|
|
147
161
|
get() {
|
|
148
162
|
if (!reflect.has(globalObject, name)) throw new errors.ReferenceError(`${name} is not defined`)
|
|
149
163
|
return reflect.get(globalObject, name, globalObject)
|
|
@@ -154,7 +168,8 @@ export function createDynamicEnvironmentRuntime({
|
|
|
154
168
|
},
|
|
155
169
|
typeof: () => reflect.has(globalObject, name) ? typeof reflect.get(globalObject, name, globalObject) : 'undefined',
|
|
156
170
|
delete: () => reflect.deleteProperty(globalObject, name),
|
|
157
|
-
|
|
171
|
+
}, undefined, strict)
|
|
172
|
+
}
|
|
158
173
|
|
|
159
174
|
function environment({ frames = [], varFrame = new internal.Map(), strict = false, origin, lexicalContext = {},
|
|
160
175
|
getThis = () => globalObject, getNewTarget = () => undefined, allowNewTarget = false,
|
|
@@ -170,6 +185,12 @@ export function createDynamicEnvironmentRuntime({
|
|
|
170
185
|
importModule,
|
|
171
186
|
internalArray,
|
|
172
187
|
expose: nativeCalls.exposedValue,
|
|
188
|
+
exposeProperty(receiver, key) {
|
|
189
|
+
if (receiver === null || receiver === undefined) {
|
|
190
|
+
throw new errors.TypeError(`Cannot read properties of ${receiver}`)
|
|
191
|
+
}
|
|
192
|
+
return nativeCalls.exposedMemberValue(receiver, reflect.get(object(receiver), key, receiver))
|
|
193
|
+
},
|
|
173
194
|
prepareCall: nativeCalls.prepareCall,
|
|
174
195
|
prepareInvocation: nativeCalls.prepareInvocation,
|
|
175
196
|
releaseMemberReceiver: nativeCalls.releaseMemberReceiver,
|
|
@@ -42,7 +42,10 @@ export function rewriteNativeCalls(tree, environment, { origin, skip, operations
|
|
|
42
42
|
parent.node.computed = true
|
|
43
43
|
parent.node.key = t.stringLiteral(path.node.name)
|
|
44
44
|
}
|
|
45
|
-
path.
|
|
45
|
+
if (path.isMemberExpression() && !t.isSuper(path.node.object) && !t.isPrivateName(path.node.property)) {
|
|
46
|
+
const key = path.node.computed ? path.node.property : t.stringLiteral(path.node.property.name)
|
|
47
|
+
path.replaceWith(t.inherits(helper('exposeProperty', [path.node.object, key]), path.node))
|
|
48
|
+
} else path.replaceWith(t.inherits(helper('expose', [path.node]), path.node))
|
|
46
49
|
path.skip()
|
|
47
50
|
}
|
|
48
51
|
const prepare = (callee, target, optional = false) => {
|
|
@@ -11,6 +11,7 @@ const exposedIntrinsics = new internal.WeakMap()
|
|
|
11
11
|
const sources = createCallableSourceRegistry()
|
|
12
12
|
const callableSources = new internal.WeakMap()
|
|
13
13
|
const callableSourceTargets = new internal.WeakMap()
|
|
14
|
+
const callableInterfaces = new internal.WeakMap()
|
|
14
15
|
const sourceTarget = origin => typeof origin === 'string' ? origin : origin?.target
|
|
15
16
|
const copyArguments = (source, start = 0, result = []) => internal.copyArray(source, start, source.length, result)
|
|
16
17
|
const invocationScopes = new AsyncLocalStorage()
|
|
@@ -114,7 +115,19 @@ export function createNativeCallAdapter({ realmFunction, intrinsicEval, reflect,
|
|
|
114
115
|
internal.weakMapSet(state.callableSources, compiled, source)
|
|
115
116
|
return compiled
|
|
116
117
|
}
|
|
117
|
-
const exposedValue = value =>
|
|
118
|
+
const exposedValue = value => {
|
|
119
|
+
const exposed = internal.mapGet(selected.exposed, value) ?? internal.weakMapGet(exposedIntrinsics, value)
|
|
120
|
+
if (exposed !== undefined) return exposed
|
|
121
|
+
if (typeof value === 'function' && !internal.weakMapHas(callableInterfaces, value)) {
|
|
122
|
+
internal.weakMapSet(callableInterfaces, value, selected)
|
|
123
|
+
}
|
|
124
|
+
return value
|
|
125
|
+
}
|
|
126
|
+
const exposedMemberValue = (receiver, value) => {
|
|
127
|
+
const owner = typeof receiver === 'function' ? internal.weakMapGet(callableInterfaces, receiver) : undefined
|
|
128
|
+
const exposed = owner === undefined ? undefined : internal.mapGet(owner.exposed, value)
|
|
129
|
+
return exposed ?? exposedValue(value)
|
|
130
|
+
}
|
|
118
131
|
const propagate = (error, origin) => {
|
|
119
132
|
recordExceptionOrigin(error, sourceTarget(origin), { reset: true })
|
|
120
133
|
for (let scope = currentScope(); scope !== undefined; scope = scope.parent) {
|
|
@@ -218,6 +231,7 @@ export function createNativeCallAdapter({ realmFunction, intrinsicEval, reflect,
|
|
|
218
231
|
return {
|
|
219
232
|
install,
|
|
220
233
|
exposedValue,
|
|
234
|
+
exposedMemberValue,
|
|
221
235
|
isEval: callee => callee === state.intrinsicEval || internal.mapHas(selected.exposed, state.intrinsicEval) && callee === internal.mapGet(selected.exposed, state.intrinsicEval),
|
|
222
236
|
evaluate(compiled, environment) {
|
|
223
237
|
// Bootstrap originals never leave this owner. Direct eval must use the
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { diagnostic } from './diagnostic.js'
|
|
2
|
-
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
3
2
|
import { createHash } from 'node:crypto'
|
|
4
|
-
import {
|
|
3
|
+
import { createPrivateAsyncLocalStorage } from './async-local-storage-intrinsics.js'
|
|
4
|
+
import { runtimeIntrinsics as internal } from './runtime-intrinsics.js'
|
|
5
5
|
|
|
6
|
+
const { Object, RegExp, isArray, numberIsSafeInteger, toNumber, toString,
|
|
7
|
+
splitString, startsWith, endsWith, sliceString, searchString, replaceString,
|
|
8
|
+
trimString, regexpExec, prependArray, join, bufferByteLength, bufferIsBuffer,
|
|
9
|
+
bufferSubarray, bufferToString } = internal
|
|
6
10
|
export const MAX_ERROR_LOG_BYTES = 4 * 1024
|
|
7
11
|
export const FAILURE_HINT_THRESHOLD = 3
|
|
8
12
|
export const LONG_CELL_CODE_UNITS = 2_000
|
|
9
13
|
const BINDING_FAILURE = Symbol('binding failure')
|
|
10
14
|
const PROGRAM_FAILURES = new internal.WeakMap()
|
|
11
15
|
const MAX_CAUSE_CODE_UNITS = 2048
|
|
12
|
-
const exceptionOrigins =
|
|
16
|
+
const exceptionOrigins = createPrivateAsyncLocalStorage()
|
|
13
17
|
const runExceptionScope = exceptionOrigins.run.bind(exceptionOrigins)
|
|
14
18
|
const currentExceptionScope = exceptionOrigins.getStore.bind(exceptionOrigins)
|
|
15
19
|
|
|
@@ -36,14 +40,14 @@ export function recordExceptionOrigin(error, origin, { reset = false, sourceFail
|
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
const SOURCE_LINE_BREAK = /\r\n|[\n\r\u2028\u2029]/
|
|
39
|
-
const sourceLines = source => source
|
|
43
|
+
const sourceLines = source => splitString(source, SOURCE_LINE_BREAK)
|
|
40
44
|
|
|
41
45
|
/** A cell position is a 1-based line and column inside its source. The column
|
|
42
46
|
* may address the position just after the last character of a line. */
|
|
43
47
|
function boundedPosition(position, lines) {
|
|
44
48
|
if (position === null || typeof position !== 'object'
|
|
45
|
-
|| !
|
|
46
|
-
|| !
|
|
49
|
+
|| !numberIsSafeInteger(position.line) || position.line < 1
|
|
50
|
+
|| !numberIsSafeInteger(position.column) || position.column < 1
|
|
47
51
|
|| position.line > lines.length || position.column > lines[position.line - 1].length + 1) return undefined
|
|
48
52
|
return { line: position.line, column: position.column }
|
|
49
53
|
}
|
|
@@ -55,14 +59,15 @@ export function cellPosition(position, source) {
|
|
|
55
59
|
|
|
56
60
|
/** Only a fact belonging to the current original source can bypass its map. */
|
|
57
61
|
export function exceptionOriginPosition(origins, source) {
|
|
58
|
-
if (!
|
|
62
|
+
if (!isArray(origins)) return undefined
|
|
59
63
|
const prefix = `eval:${createHash('sha256').update(source).digest('hex')}:`
|
|
60
64
|
const lines = sourceLines(source)
|
|
61
|
-
for (
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
for (let index = 0; index < origins.length; index += 1) {
|
|
66
|
+
const origin = origins[index]
|
|
67
|
+
if (typeof origin !== 'string' || !startsWith(origin, prefix)) continue
|
|
68
|
+
const match = regexpExec(/^(\d+):(\d+)$/, sliceString(origin, prefix.length))
|
|
64
69
|
if (match === null) continue
|
|
65
|
-
const position = boundedPosition({ line:
|
|
70
|
+
const position = boundedPosition({ line: toNumber(match[1]), column: toNumber(match[2]) }, lines)
|
|
66
71
|
if (position !== undefined) return position
|
|
67
72
|
}
|
|
68
73
|
return undefined
|
|
@@ -124,7 +129,7 @@ export function messageOf(error, fallback = 'Unprintable error') {
|
|
|
124
129
|
const message = safeProperty(error, 'message')
|
|
125
130
|
if (typeof message === 'string') return message
|
|
126
131
|
try {
|
|
127
|
-
return
|
|
132
|
+
return toString(error)
|
|
128
133
|
} catch {
|
|
129
134
|
return fallback
|
|
130
135
|
}
|
|
@@ -139,20 +144,21 @@ export function processExitDescription(code, signal) {
|
|
|
139
144
|
|
|
140
145
|
export function firstLine(value, fallback = undefined) {
|
|
141
146
|
if (typeof value !== 'string') return fallback
|
|
142
|
-
const line = value
|
|
147
|
+
const line = splitString(value, /[\r\n]/, 1)[0]
|
|
143
148
|
return line.length > 0 ? line : fallback
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
export function oneLineMessage(error) {
|
|
147
|
-
return firstLine(messageOf(error), 'Unknown error')
|
|
152
|
+
return replaceString(firstLine(messageOf(error), 'Unknown error'), /\s+\(\d+:\d+\)$/, '')
|
|
148
153
|
}
|
|
149
154
|
|
|
150
155
|
/** Return DSH's reported missing-description property path when it is actionable. */
|
|
151
156
|
export function missingDescriptionPath(error) {
|
|
152
|
-
const
|
|
153
|
-
|
|
157
|
+
const pattern = /missing required property ["']([^"']+)["']/g
|
|
158
|
+
const message = messageOf(error)
|
|
159
|
+
for (let match = regexpExec(pattern, message); match !== null; match = regexpExec(pattern, message)) {
|
|
154
160
|
const path = match[1]
|
|
155
|
-
if (path === 'description' ||
|
|
161
|
+
if (path === 'description' || endsWith(path, '.description')) return path
|
|
156
162
|
}
|
|
157
163
|
return undefined
|
|
158
164
|
}
|
|
@@ -167,18 +173,18 @@ export function limitLogs(logs) {
|
|
|
167
173
|
let bytes = 0
|
|
168
174
|
const tail = []
|
|
169
175
|
for (let index = logs.length - 1; index >= 0; index -= 1) {
|
|
170
|
-
const size =
|
|
176
|
+
const size = bufferByteLength(logs[index], 'utf8') + 1
|
|
171
177
|
if (bytes + size > MAX_ERROR_LOG_BYTES) break
|
|
172
178
|
bytes += size
|
|
173
|
-
tail
|
|
179
|
+
prependArray(tail, logs[index])
|
|
174
180
|
}
|
|
175
181
|
return tail
|
|
176
182
|
}
|
|
177
183
|
|
|
178
184
|
/** A frame is only useful when its 1-based line and column are safe integers. */
|
|
179
185
|
function structuralPosition(position) {
|
|
180
|
-
return
|
|
181
|
-
&&
|
|
186
|
+
return numberIsSafeInteger(position.line) && position.line >= 1
|
|
187
|
+
&& numberIsSafeInteger(position.column) && position.column >= 1 ? position : undefined
|
|
182
188
|
}
|
|
183
189
|
|
|
184
190
|
/** Extract the first frame for the active cell. The frame is reported in the
|
|
@@ -187,10 +193,10 @@ function structuralPosition(position) {
|
|
|
187
193
|
export function errorPosition(error, filename, source) {
|
|
188
194
|
const stack = safeProperty(error, 'stack')
|
|
189
195
|
if (typeof stack !== 'string') return undefined
|
|
190
|
-
const escaped = filename
|
|
191
|
-
const match = new RegExp(`${escaped}:(\\d+):(\\d+)`)
|
|
196
|
+
const escaped = replaceString(filename, /[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
197
|
+
const match = regexpExec(new RegExp(`${escaped}:(\\d+):(\\d+)`), stack)
|
|
192
198
|
if (match === null) return undefined
|
|
193
|
-
const position = { line:
|
|
199
|
+
const position = { line: toNumber(match[1]), column: toNumber(match[2]) }
|
|
194
200
|
return source === undefined ? structuralPosition(position) : cellPosition(position, source)
|
|
195
201
|
}
|
|
196
202
|
|
|
@@ -206,12 +212,14 @@ export function errorDetails(error, filename, source) {
|
|
|
206
212
|
const causeMessage = firstLine(safeProperty(candidate, 'message'))
|
|
207
213
|
const causeCode = firstLine(safeProperty(candidate, 'code'))
|
|
208
214
|
const stderr = safeProperty(error, 'stderr')
|
|
209
|
-
const stderrText = typeof stderr === 'string' ? stderr
|
|
210
|
-
:
|
|
211
|
-
|
|
212
|
-
const
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
+
const stderrText = typeof stderr === 'string' ? sliceString(stderr, 0, MAX_CAUSE_CODE_UNITS)
|
|
216
|
+
: bufferIsBuffer(stderr)
|
|
217
|
+
? bufferToString(bufferSubarray(stderr, 0, MAX_CAUSE_CODE_UNITS), 'utf8') : ''
|
|
218
|
+
const boundedMessage = sliceString(message, 0, MAX_CAUSE_CODE_UNITS)
|
|
219
|
+
const continuation = searchString(boundedMessage, /[\r\n]/)
|
|
220
|
+
const detailParts = [continuation < 0 ? '' : sliceString(boundedMessage, continuation + 1), stderrText]
|
|
221
|
+
const detail = trimString(replaceString(
|
|
222
|
+
sliceString(join(detailParts, ' '), 0, MAX_CAUSE_CODE_UNITS), /\s+/g, ' '))
|
|
215
223
|
const cause = causeMessage === undefined
|
|
216
224
|
? detail.length === 0 ? undefined : { message: detail }
|
|
217
225
|
: {
|
|
@@ -3,6 +3,7 @@ import { EventEmitter } from 'node:events'
|
|
|
3
3
|
import { resolve } from 'node:path'
|
|
4
4
|
import { PassThrough } from 'node:stream'
|
|
5
5
|
import { pathToFileURL } from 'node:url'
|
|
6
|
+
import { helperProcessEnvironment, isElectronHost } from './worker-environment.js'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Fallback bound for a helper that answers the stop request and then keeps the
|
|
@@ -43,6 +44,7 @@ export class IsolatedWorker extends EventEmitter {
|
|
|
43
44
|
this.stderr = new PassThrough()
|
|
44
45
|
this.protocol = protocol
|
|
45
46
|
this.exited = false
|
|
47
|
+
this.exitedPromise = new Promise(resolve => { this.resolveExited = resolve })
|
|
46
48
|
this.exitCode = undefined
|
|
47
49
|
this.exitSignal = undefined
|
|
48
50
|
this.released = false
|
|
@@ -63,12 +65,15 @@ export class IsolatedWorker extends EventEmitter {
|
|
|
63
65
|
}
|
|
64
66
|
// child_process.fork mutates the env object it receives by copying
|
|
65
67
|
// NODE_V8_COVERAGE into it. Snapshot the caller's projection first so the
|
|
66
|
-
// inner Worker receives exactly the projected user environment
|
|
67
|
-
// helper
|
|
68
|
-
const
|
|
68
|
+
// inner Worker receives exactly the projected user environment. An Electron
|
|
69
|
+
// Host also needs a helper-only startup override; it must not reach user code.
|
|
70
|
+
const electronHost = isElectronHost()
|
|
71
|
+
const projectedEnvironment = env === undefined && electronHost ? process.env : env
|
|
72
|
+
const workerEnvironment = projectedEnvironment === undefined ? undefined : { ...projectedEnvironment }
|
|
73
|
+
const forkEnvironment = helperProcessEnvironment(projectedEnvironment, process.platform, electronHost)
|
|
69
74
|
this.child = fork(helper, [], {
|
|
70
75
|
stdio: ['ignore', 'pipe', 'pipe', 'ipc'],
|
|
71
|
-
env,
|
|
76
|
+
env: forkEnvironment,
|
|
72
77
|
serialization: 'advanced',
|
|
73
78
|
// The helper starts with the host's own startup arguments removed, matching
|
|
74
79
|
// the isolation the in-host worker had with execArgv: [].
|
|
@@ -80,6 +85,7 @@ export class IsolatedWorker extends EventEmitter {
|
|
|
80
85
|
this.exited = true
|
|
81
86
|
this.exitCode = code
|
|
82
87
|
this.exitSignal = signal
|
|
88
|
+
this.resolveExited()
|
|
83
89
|
this.#rejectUtilizationRequests(new Error('kernel helper exited before answering utilization sampling'))
|
|
84
90
|
})
|
|
85
91
|
// A ChildProcess 'close' follows 'exit' once its stdio has really ended, which
|
|
@@ -264,7 +270,7 @@ export class IsolatedWorker extends EventEmitter {
|
|
|
264
270
|
const started = Date.now()
|
|
265
271
|
const killAt = started + ISOLATED_RELEASE_MS
|
|
266
272
|
const confirmBy = started + ISOLATED_STOP_MS
|
|
267
|
-
const exited =
|
|
273
|
+
const exited = this.exitedPromise
|
|
268
274
|
const onReleased = message => {
|
|
269
275
|
if (message?.type === 'worker-message' && message.value?.type === 'shutdown-released') this.released = true
|
|
270
276
|
}
|
|
@@ -278,18 +284,19 @@ export class IsolatedWorker extends EventEmitter {
|
|
|
278
284
|
// The channel is closed; the kill path below owns the instance.
|
|
279
285
|
}
|
|
280
286
|
if (!await leftWithin(this, remaining(killAt))) {
|
|
287
|
+
let killFailure
|
|
281
288
|
if (!this.exited) {
|
|
282
289
|
try {
|
|
283
290
|
await this.kill()
|
|
284
291
|
} catch (error) {
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
this
|
|
288
|
-
|
|
292
|
+
// On Windows a process that has already left can report a refused
|
|
293
|
+
// kill before Node delivers its exit event. The real exit observation
|
|
294
|
+
// decides whether this instance was reclaimed.
|
|
295
|
+
killFailure = error
|
|
289
296
|
}
|
|
290
297
|
}
|
|
291
298
|
if (!this.exited && !await leftWithin(this, remaining(confirmBy))) {
|
|
292
|
-
const failure = new Error('kernel helper did not exit before the close deadline')
|
|
299
|
+
const failure = killFailure ?? new Error('kernel helper did not exit before the close deadline')
|
|
293
300
|
this.reclamationFailure = failure
|
|
294
301
|
throw failure
|
|
295
302
|
}
|