1'use strict'; 2 3const assert = require('internal/assert'); 4const { 5 ArrayIsArray, 6 ArrayPrototypeForEach, 7 ArrayPrototypeIndexOf, 8 ArrayPrototypeSome, 9 ObjectCreate, 10 ObjectDefineProperty, 11 ObjectGetPrototypeOf, 12 ObjectSetPrototypeOf, 13 ReflectApply, 14 SafePromiseAllReturnVoid, 15 SafeWeakMap, 16 Symbol, 17 SymbolToStringTag, 18 TypeError, 19} = primordials; 20 21const { isContext } = internalBinding('contextify'); 22const { 23 isModuleNamespaceObject, 24} = require('internal/util/types'); 25const { 26 customInspectSymbol, 27 emitExperimentalWarning, 28 getConstructorOf, 29 kEmptyObject, 30} = require('internal/util'); 31const { 32 ERR_INVALID_ARG_TYPE, 33 ERR_INVALID_ARG_VALUE, 34 ERR_VM_MODULE_ALREADY_LINKED, 35 ERR_VM_MODULE_DIFFERENT_CONTEXT, 36 ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA, 37 ERR_VM_MODULE_LINK_FAILURE, 38 ERR_VM_MODULE_NOT_MODULE, 39 ERR_VM_MODULE_STATUS, 40} = require('internal/errors').codes; 41const { 42 validateBoolean, 43 validateBuffer, 44 validateFunction, 45 validateInt32, 46 validateObject, 47 validateUint32, 48 validateString, 49} = require('internal/validators'); 50 51const binding = internalBinding('module_wrap'); 52const { 53 ModuleWrap, 54 kUninstantiated, 55 kInstantiating, 56 kInstantiated, 57 kEvaluating, 58 kEvaluated, 59 kErrored, 60} = binding; 61 62const STATUS_MAP = { 63 [kUninstantiated]: 'unlinked', 64 [kInstantiating]: 'linking', 65 [kInstantiated]: 'linked', 66 [kEvaluating]: 'evaluating', 67 [kEvaluated]: 'evaluated', 68 [kErrored]: 'errored', 69}; 70 71let globalModuleId = 0; 72const defaultModuleName = 'vm:module'; 73const wrapToModuleMap = new SafeWeakMap(); 74 75const kWrap = Symbol('kWrap'); 76const kContext = Symbol('kContext'); 77const kPerContextModuleId = Symbol('kPerContextModuleId'); 78const kLink = Symbol('kLink'); 79 80class Module { 81 constructor(options) { 82 emitExperimentalWarning('VM Modules'); 83 84 if (new.target === Module) { 85 // eslint-disable-next-line no-restricted-syntax 86 throw new TypeError('Module is not a constructor'); 87 } 88 89 const { 90 context, 91 sourceText, 92 syntheticExportNames, 93 syntheticEvaluationSteps, 94 } = options; 95 96 if (context !== undefined) { 97 validateObject(context, 'context'); 98 if (!isContext(context)) { 99 throw new ERR_INVALID_ARG_TYPE('options.context', 'vm.Context', 100 context); 101 } 102 } 103 104 let { identifier } = options; 105 if (identifier !== undefined) { 106 validateString(identifier, 'options.identifier'); 107 } else if (context === undefined) { 108 identifier = `${defaultModuleName}(${globalModuleId++})`; 109 } else if (context[kPerContextModuleId] !== undefined) { 110 const curId = context[kPerContextModuleId]; 111 identifier = `${defaultModuleName}(${curId})`; 112 context[kPerContextModuleId] += 1; 113 } else { 114 identifier = `${defaultModuleName}(0)`; 115 ObjectDefineProperty(context, kPerContextModuleId, { 116 __proto__: null, 117 value: 1, 118 writable: true, 119 enumerable: false, 120 configurable: true, 121 }); 122 } 123 124 if (sourceText !== undefined) { 125 this[kWrap] = new ModuleWrap(identifier, context, sourceText, 126 options.lineOffset, options.columnOffset, 127 options.cachedData); 128 129 binding.callbackMap.set(this[kWrap], { 130 initializeImportMeta: options.initializeImportMeta, 131 importModuleDynamically: options.importModuleDynamically ? 132 importModuleDynamicallyWrap(options.importModuleDynamically) : 133 undefined, 134 }); 135 } else { 136 assert(syntheticEvaluationSteps); 137 this[kWrap] = new ModuleWrap(identifier, context, 138 syntheticExportNames, 139 syntheticEvaluationSteps); 140 } 141 142 wrapToModuleMap.set(this[kWrap], this); 143 144 this[kContext] = context; 145 } 146 147 get identifier() { 148 if (this[kWrap] === undefined) { 149 throw new ERR_VM_MODULE_NOT_MODULE(); 150 } 151 return this[kWrap].url; 152 } 153 154 get context() { 155 if (this[kWrap] === undefined) { 156 throw new ERR_VM_MODULE_NOT_MODULE(); 157 } 158 return this[kContext]; 159 } 160 161 get namespace() { 162 if (this[kWrap] === undefined) { 163 throw new ERR_VM_MODULE_NOT_MODULE(); 164 } 165 if (this[kWrap].getStatus() < kInstantiated) { 166 throw new ERR_VM_MODULE_STATUS('must not be unlinked or linking'); 167 } 168 return this[kWrap].getNamespace(); 169 } 170 171 get status() { 172 if (this[kWrap] === undefined) { 173 throw new ERR_VM_MODULE_NOT_MODULE(); 174 } 175 return STATUS_MAP[this[kWrap].getStatus()]; 176 } 177 178 get error() { 179 if (this[kWrap] === undefined) { 180 throw new ERR_VM_MODULE_NOT_MODULE(); 181 } 182 if (this[kWrap].getStatus() !== kErrored) { 183 throw new ERR_VM_MODULE_STATUS('must be errored'); 184 } 185 return this[kWrap].getError(); 186 } 187 188 async link(linker) { 189 if (this[kWrap] === undefined) { 190 throw new ERR_VM_MODULE_NOT_MODULE(); 191 } 192 validateFunction(linker, 'linker'); 193 if (this.status === 'linked') { 194 throw new ERR_VM_MODULE_ALREADY_LINKED(); 195 } 196 if (this.status !== 'unlinked') { 197 throw new ERR_VM_MODULE_STATUS('must be unlinked'); 198 } 199 await this[kLink](linker); 200 this[kWrap].instantiate(); 201 } 202 203 async evaluate(options = kEmptyObject) { 204 if (this[kWrap] === undefined) { 205 throw new ERR_VM_MODULE_NOT_MODULE(); 206 } 207 208 validateObject(options, 'options'); 209 210 let timeout = options.timeout; 211 if (timeout === undefined) { 212 timeout = -1; 213 } else { 214 validateUint32(timeout, 'options.timeout', true); 215 } 216 const { breakOnSigint = false } = options; 217 validateBoolean(breakOnSigint, 'options.breakOnSigint'); 218 const status = this[kWrap].getStatus(); 219 if (status !== kInstantiated && 220 status !== kEvaluated && 221 status !== kErrored) { 222 throw new ERR_VM_MODULE_STATUS( 223 'must be one of linked, evaluated, or errored', 224 ); 225 } 226 await this[kWrap].evaluate(timeout, breakOnSigint); 227 } 228 229 [customInspectSymbol](depth, options) { 230 if (this[kWrap] === undefined) { 231 throw new ERR_VM_MODULE_NOT_MODULE(); 232 } 233 if (typeof depth === 'number' && depth < 0) 234 return this; 235 236 const constructor = getConstructorOf(this) || Module; 237 const o = ObjectCreate({ constructor }); 238 o.status = this.status; 239 o.identifier = this.identifier; 240 o.context = this.context; 241 242 ObjectSetPrototypeOf(o, ObjectGetPrototypeOf(this)); 243 ObjectDefineProperty(o, SymbolToStringTag, { 244 __proto__: null, 245 value: constructor.name, 246 configurable: true, 247 }); 248 249 // Lazy to avoid circular dependency 250 const { inspect } = require('internal/util/inspect'); 251 return inspect(o, { ...options, customInspect: false }); 252 } 253} 254 255const kDependencySpecifiers = Symbol('kDependencySpecifiers'); 256const kNoError = Symbol('kNoError'); 257 258class SourceTextModule extends Module { 259 #error = kNoError; 260 #statusOverride; 261 262 constructor(sourceText, options = kEmptyObject) { 263 validateString(sourceText, 'sourceText'); 264 validateObject(options, 'options'); 265 266 const { 267 lineOffset = 0, 268 columnOffset = 0, 269 initializeImportMeta, 270 importModuleDynamically, 271 context, 272 identifier, 273 cachedData, 274 } = options; 275 276 validateInt32(lineOffset, 'options.lineOffset'); 277 validateInt32(columnOffset, 'options.columnOffset'); 278 279 if (initializeImportMeta !== undefined) { 280 validateFunction(initializeImportMeta, 'options.initializeImportMeta'); 281 } 282 283 if (importModuleDynamically !== undefined) { 284 validateFunction(importModuleDynamically, 'options.importModuleDynamically'); 285 } 286 287 if (cachedData !== undefined) { 288 validateBuffer(cachedData, 'options.cachedData'); 289 } 290 291 super({ 292 sourceText, 293 context, 294 identifier, 295 lineOffset, 296 columnOffset, 297 cachedData, 298 initializeImportMeta, 299 importModuleDynamically, 300 }); 301 302 this[kLink] = async (linker) => { 303 this.#statusOverride = 'linking'; 304 305 const promises = this[kWrap].link(async (identifier, assert) => { 306 const module = await linker(identifier, this, { assert }); 307 if (module[kWrap] === undefined) { 308 throw new ERR_VM_MODULE_NOT_MODULE(); 309 } 310 if (module.context !== this.context) { 311 throw new ERR_VM_MODULE_DIFFERENT_CONTEXT(); 312 } 313 if (module.status === 'errored') { 314 throw new ERR_VM_MODULE_LINK_FAILURE(`request for '${identifier}' resolved to an errored module`, module.error); 315 } 316 if (module.status === 'unlinked') { 317 await module[kLink](linker); 318 } 319 return module[kWrap]; 320 }); 321 322 try { 323 if (promises !== undefined) { 324 await SafePromiseAllReturnVoid(promises); 325 } 326 } catch (e) { 327 this.#error = e; 328 throw e; 329 } finally { 330 this.#statusOverride = undefined; 331 } 332 }; 333 334 this[kDependencySpecifiers] = undefined; 335 } 336 337 get dependencySpecifiers() { 338 if (this[kWrap] === undefined) { 339 throw new ERR_VM_MODULE_NOT_MODULE(); 340 } 341 if (this[kDependencySpecifiers] === undefined) { 342 this[kDependencySpecifiers] = this[kWrap].getStaticDependencySpecifiers(); 343 } 344 return this[kDependencySpecifiers]; 345 } 346 347 get status() { 348 if (this[kWrap] === undefined) { 349 throw new ERR_VM_MODULE_NOT_MODULE(); 350 } 351 if (this.#error !== kNoError) { 352 return 'errored'; 353 } 354 if (this.#statusOverride) { 355 return this.#statusOverride; 356 } 357 return super.status; 358 } 359 360 get error() { 361 if (this[kWrap] === undefined) { 362 throw new ERR_VM_MODULE_NOT_MODULE(); 363 } 364 if (this.#error !== kNoError) { 365 return this.#error; 366 } 367 return super.error; 368 } 369 370 createCachedData() { 371 const { status } = this; 372 if (status === 'evaluating' || 373 status === 'evaluated' || 374 status === 'errored') { 375 throw new ERR_VM_MODULE_CANNOT_CREATE_CACHED_DATA(); 376 } 377 return this[kWrap].createCachedData(); 378 } 379} 380 381class SyntheticModule extends Module { 382 constructor(exportNames, evaluateCallback, options = kEmptyObject) { 383 if (!ArrayIsArray(exportNames) || 384 ArrayPrototypeSome(exportNames, (e) => typeof e !== 'string')) { 385 throw new ERR_INVALID_ARG_TYPE('exportNames', 386 'Array of unique strings', 387 exportNames); 388 } else { 389 ArrayPrototypeForEach(exportNames, (name, i) => { 390 if (ArrayPrototypeIndexOf(exportNames, name, i + 1) !== -1) { 391 throw new ERR_INVALID_ARG_VALUE(`exportNames.${name}`, 392 name, 393 'is duplicated'); 394 } 395 }); 396 } 397 validateFunction(evaluateCallback, 'evaluateCallback'); 398 399 validateObject(options, 'options'); 400 401 const { context, identifier } = options; 402 403 super({ 404 syntheticExportNames: exportNames, 405 syntheticEvaluationSteps: evaluateCallback, 406 context, 407 identifier, 408 }); 409 410 this[kLink] = () => this[kWrap].link(() => { 411 assert.fail('link callback should not be called'); 412 }); 413 } 414 415 setExport(name, value) { 416 if (this[kWrap] === undefined) { 417 throw new ERR_VM_MODULE_NOT_MODULE(); 418 } 419 validateString(name, 'name'); 420 if (this[kWrap].getStatus() < kInstantiated) { 421 throw new ERR_VM_MODULE_STATUS('must be linked'); 422 } 423 this[kWrap].setExport(name, value); 424 } 425} 426 427function importModuleDynamicallyWrap(importModuleDynamically) { 428 const importModuleDynamicallyWrapper = async (...args) => { 429 const m = await ReflectApply(importModuleDynamically, this, args); 430 if (isModuleNamespaceObject(m)) { 431 return m; 432 } 433 if (!m || m[kWrap] === undefined) { 434 throw new ERR_VM_MODULE_NOT_MODULE(); 435 } 436 if (m.status === 'errored') { 437 throw m.error; 438 } 439 return m.namespace; 440 }; 441 return importModuleDynamicallyWrapper; 442} 443 444module.exports = { 445 Module, 446 SourceTextModule, 447 SyntheticModule, 448 importModuleDynamicallyWrap, 449 getModuleFromWrap: (wrap) => wrapToModuleMap.get(wrap), 450}; 451