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