@fast-check/poisoning
Version:
Set of utilities to ease detection and revert of poisoning
85 lines (84 loc) • 3.65 kB
JavaScript
import { PoisoningFreeArray, MapSymbol, SortSymbol, ShiftSymbol, PushSymbol } from './PoisoningFreeArray.js';
import { GetSymbol, HasSymbol, SetSymbol, PoisoningFreeMap } from './PoisoningFreeMap.js';
import { AddSymbol, HasSymbol as SetHasSymbol, PoisoningFreeSet } from './PoisoningFreeSet.js';
const SString = String;
const safeObjectGetOwnPropertyDescriptors = Object.getOwnPropertyDescriptors;
const safeObjectGetOwnPropertyNames = Object.getOwnPropertyNames;
const safeObjectGetOwnPropertySymbols = Object.getOwnPropertySymbols;
function compareKeys(keyA, keyB) {
const sA = SString(keyA[0]);
const sB = SString(keyB[0]);
return sA < sB ? -1 : sA > sB ? 1 : 0;
}
function extractAllDescriptorsDetails(instance) {
const descriptors = safeObjectGetOwnPropertyDescriptors(instance);
const allDescriptors = PoisoningFreeArray.from([
...safeObjectGetOwnPropertyNames(descriptors),
...safeObjectGetOwnPropertySymbols(descriptors),
]);
const allDescriptorsDetails = PoisoningFreeArray.from(allDescriptors[MapSymbol]((name) => [
name,
descriptors[name],
]));
return allDescriptorsDetails[SortSymbol](compareKeys);
}
function flagRootRecursively(knownGlobals, instance, currentRoot) {
const storedGlobal = knownGlobals[GetSymbol](instance);
if (storedGlobal === undefined) {
return;
}
if (storedGlobal.rootAncestors[SetHasSymbol](currentRoot)) {
return;
}
storedGlobal.rootAncestors[AddSymbol](currentRoot);
if (storedGlobal.depth <= 1) {
return;
}
for (const [, descriptor] of storedGlobal.properties) {
flagRootRecursively(knownGlobals, descriptor.value, currentRoot);
}
}
export function captureAllGlobals() {
const knownGlobals = PoisoningFreeMap.from();
const nextCaptures = PoisoningFreeArray.from([
{ instance: globalThis, name: 'globalThis', currentDepth: 0, lastRootInPath: 'globalThis' },
]);
while (nextCaptures.length !== 0) {
const { instance, name, currentDepth, lastRootInPath } = nextCaptures[ShiftSymbol]();
if (typeof instance !== 'function' && typeof instance !== 'object') {
continue;
}
if (instance === null || instance === undefined) {
continue;
}
if (knownGlobals[HasSymbol](instance)) {
flagRootRecursively(knownGlobals, instance, lastRootInPath);
continue;
}
const allDescriptorsDetails = extractAllDescriptorsDetails(instance);
const localGlobal = {
name,
depth: currentDepth,
properties: PoisoningFreeMap.from(),
rootAncestors: PoisoningFreeSet.from([lastRootInPath]),
};
knownGlobals[SetSymbol](instance, localGlobal);
for (let index = 0; index !== allDescriptorsDetails.length; ++index) {
const descriptorName = allDescriptorsDetails[index][0];
const descriptor = allDescriptorsDetails[index][1];
localGlobal.properties[SetSymbol](descriptorName, descriptor);
if (typeof descriptorName === 'symbol') {
continue;
}
const subGlobalName = currentDepth !== 0 ? name + '.' + SString(descriptorName) : SString(descriptorName);
const newLastRootInPath = currentDepth <= 1 ? name : lastRootInPath;
nextCaptures[PushSymbol]({
instance: descriptor.value,
name: subGlobalName,
currentDepth: currentDepth + 1,
lastRootInPath: newLastRootInPath,
});
}
}
return knownGlobals;
}