|
| 1 | +// Flags: --no-warnings |
| 2 | +import '../common/index.mjs'; |
| 3 | +import { builtinModules } from 'node:module'; |
| 4 | + |
| 5 | +// This test recursively checks all properties on builtin modules and ensures that accessing them |
| 6 | +// does not throw. The only valid reason for property accessor to throw is "invalid this" error |
| 7 | +// when property must be not accessible directly on the prototype. |
| 8 | + |
| 9 | +// Normally we don't have properties nested too deep |
| 10 | +const MAX_NESTING_DEPTH = 16; |
| 11 | + |
| 12 | +// Some properties can be present or absent depending on the environment |
| 13 | +const knownExceptions = { |
| 14 | + module: [ |
| 15 | + // The _cache and _pathCache are populated with local paths |
| 16 | + // Also, trying to access _cache.<common/index.js>.exports.then would throw |
| 17 | + /^\{module\}(?:\.Module|\.default|)\.(?:_cache|_pathCache)\./, |
| 18 | + ], |
| 19 | +}; |
| 20 | + |
| 21 | +function isValid({ key, moduleName, path, propName, value, obj, fullPath }) { |
| 22 | + if (knownExceptions[moduleName]) |
| 23 | + for (const skipRegex of knownExceptions[moduleName]) |
| 24 | + if (skipRegex.test(fullPath)) |
| 25 | + return false; |
| 26 | + |
| 27 | + return true; |
| 28 | +} |
| 29 | + |
| 30 | +async function buildList(obj, moduleName, path = [], visited = new WeakMap()) { |
| 31 | + if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) return []; |
| 32 | + |
| 33 | + if (path.length > MAX_NESTING_DEPTH) { |
| 34 | + throw new Error(`Too deeply nested property in ${moduleName}: ${path.join('.')}`); |
| 35 | + } |
| 36 | + |
| 37 | + // Exclude circular references |
| 38 | + // Don't use plain Set, because same object can be exposed under different paths |
| 39 | + if (visited.has(obj)) { |
| 40 | + const visitedPaths = visited.get(obj); |
| 41 | + if (visitedPaths.some((prev) => prev.length <= path.length && prev.every((seg, i) => seg === path[i]))) { |
| 42 | + return []; |
| 43 | + } |
| 44 | + visitedPaths.push(path); |
| 45 | + } else { |
| 46 | + visited.set(obj, [path]); |
| 47 | + } |
| 48 | + |
| 49 | + const paths = []; |
| 50 | + const deeperCalls = []; |
| 51 | + |
| 52 | + for (const key of Reflect.ownKeys(obj)) { |
| 53 | + const propName = typeof key === 'symbol' ? `[${key.description}]` : key; |
| 54 | + const fullPath = `{${moduleName}}.${path.join('.')}${path.length ? '.' : ''}${propName}`; |
| 55 | + |
| 56 | + if (!isValid({ key, moduleName, path, propName, obj, fullPath })) { |
| 57 | + continue; |
| 58 | + } |
| 59 | + |
| 60 | + let value; |
| 61 | + try { |
| 62 | + value = await obj[key]; |
| 63 | + } catch (cause) { |
| 64 | + // Accessing some properties directly on the prototype may throw or reject |
| 65 | + // Throw informative errors if access failed anywhere/anyhow else |
| 66 | + if (cause.name !== 'TypeError') { |
| 67 | + throw new Error(`Access to ${fullPath} failed with name=${cause.name}`, { cause }); |
| 68 | + } |
| 69 | + if (cause.code !== 'ERR_INVALID_THIS' && cause.code !== undefined) { |
| 70 | + throw new Error(`Access to ${fullPath} failed with code=${cause.code}`, { cause }); |
| 71 | + } |
| 72 | + if (path.at(-1) !== 'prototype') { |
| 73 | + throw new Error(`Access to ${fullPath} failed but it's not on prototype`, { cause }); |
| 74 | + } |
| 75 | + } |
| 76 | + paths.push(fullPath); |
| 77 | + |
| 78 | + if ((typeof value === 'object' || typeof value === 'function') && value !== obj) { |
| 79 | + deeperCalls.push(buildList(value, moduleName, [...path, propName], visited)); |
| 80 | + } |
| 81 | + } |
| 82 | + |
| 83 | + return [...paths, ...(await Promise.all(deeperCalls)).flat()]; |
| 84 | +} |
| 85 | + |
| 86 | +let total = 0; |
| 87 | + |
| 88 | +await Promise.all(builtinModules.map(async (moduleName) => { |
| 89 | + const module = await import(moduleName); |
| 90 | + const { length } = await buildList(module, moduleName); |
| 91 | + total += length; |
| 92 | +})); |
| 93 | + |
| 94 | +console.log(`Checked ${total} properties across ${builtinModules.length} modules.`); |
0 commit comments