Last active
September 10, 2026 02:36
-
-
Save heimskr/2718b749af45f16753fd64aa74de22a0 to your computer and use it in GitHub Desktop.
Module loader fuzzer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bun | |
| import {mkdirSync, writeFileSync} from "node:fs"; | |
| if (process.argv.length < 3) { | |
| console.log("Usage: ./fuzzer.js <module count> <directory name> [\"printless\"]"); | |
| process.exit(0); | |
| } | |
| const moduleCount = process.argv[2]; | |
| const directoryName = process.argv[3] || "fuzz"; | |
| const printless = process.argv[4] == "printless"; | |
| const maxValues = 10; | |
| const linkFactor = 2; | |
| const tlaChance = 0.333; | |
| const promiseChance = 0.2; | |
| const dynamicChance = 0.25; | |
| process.on("SIGINT", () => { | |
| process.stdout.write("\x1b[?25h"); | |
| process.exit(0); | |
| }); | |
| let objectCount = parseInt(moduleCount); | |
| if (isNaN(objectCount)) { | |
| objectCount = 10; | |
| } | |
| console.log("Making objects..."); | |
| const objects = makeObjects(objectCount); | |
| console.log("Adding random links..."); | |
| addRandomLinks(objects, objectCount * linkFactor); | |
| console.log("Connecting components..."); | |
| connectComponents(objects); | |
| console.log("Connecting entry node..."); | |
| connectEntry(objects); | |
| console.log("Filling source..."); | |
| const sources = fillSource(objects); | |
| console.log("Writing files..."); | |
| mkdirSync(directoryName); | |
| sources.forEach((source, i) => { | |
| writeFileSync(`${directoryName}/${i}.mjs`, source); | |
| }); | |
| writeFileSync(`${directoryName}/_graph.dot`, toDot(objects)); | |
| console.log("Done!"); | |
| function fillSource(objects) { | |
| const valueArray = [...Array(objects.length)].map(() => []); | |
| const valueMap = {}; | |
| objects.forEach(node => { | |
| for (let i = 0; i < node.valueCount; ++i) { | |
| const value = { | |
| name: `value${node.index}_${i}`, | |
| isPromise: Math.random() < promiseChance, | |
| value: rand(10, 100), | |
| definer: node.index, | |
| }; | |
| valueMap[value.name] = value; | |
| valueArray[node.index].push(value); | |
| } | |
| }); | |
| let i = 0; | |
| process.stdout.write("\x1b[?25l"); | |
| for (const node of objects) { | |
| process.stdout.write(`\x1b[2K\x1b[G${i}/${objects.length}`); | |
| const availableValues = []; | |
| const name = `${node.index}.mjs`; | |
| if (!printless) { | |
| node.source += `if (typeof print === "undefined") { globalThis.print = console.log; }\n`; | |
| node.source += `print("${name} start");\n`; | |
| } | |
| node.source += Array.from(node.out).map(neighbor => { | |
| const indices = shuffle([...Array(neighbor.valueCount)].map((_, i) => i)).slice(0, rand(1, neighbor.valueCount)).sort(); | |
| return indices.map((i) => { | |
| const valueName = `value${neighbor.index}_${i}`; | |
| availableValues.push(valueName); | |
| if (node.hasTLA && Math.random() < dynamicChance) { | |
| return `const {${valueName}} = await import("./${neighbor.index}.mjs");`; | |
| } else { | |
| return `import {${valueName}} from "./${neighbor.index}.mjs";`; | |
| } | |
| }).join("\n"); | |
| }).join("\n") + "\n"; | |
| if (!printless) node.source += `print("${name} imports done");\n`; | |
| shuffle(availableValues); | |
| const chooseValue = () => { | |
| for (let i = 0; i < availableValues.length; ++i) { | |
| const candidate = valueMap[availableValues[i]]; | |
| if (!pathExists(objects, candidate.definer, node.index)) { | |
| availableValues.splice(i, 1); | |
| return candidate; | |
| } | |
| } | |
| return {name: rand(200, 299), isPromise: false, value: rand(200, 299)}; | |
| }; | |
| const wrap = (text, isPromise) => { | |
| return isPromise ? `Promise.resolve(${text})` : text; | |
| }; | |
| for (const value of valueArray[node.index]) { | |
| const numberSummand = rand(300, 399); | |
| const valueSummand = chooseValue(); | |
| if (node.hasTLA) { | |
| node.source += `export const ${value.name} = ${wrap(`${numberSummand} + await ${valueSummand.name}`, value.isPromise)};\n`; | |
| if (!printless) node.source += `print("${name}: ${value.name} is", await ${value.name});\n`; | |
| } else if (valueSummand.isPromise) { | |
| if (!printless) { | |
| node.source += `export const ${value.name} = ${valueSummand.name}.then(v => { print("${name}: ${value.name} is", v + ${numberSummand}); return v + ${numberSummand}; });\n`; | |
| } else { | |
| node.source += `export const ${value.name} = ${valueSummand.name}.then(v => { return v + ${numberSummand}; });\n`; | |
| } | |
| value.isPromise = true; | |
| } else if (value.isPromise) { | |
| if (!printless) { | |
| node.source += `export const ${value.name} = Promise.resolve(${value.value}).then(v => { print("${name}: ${value.name} is", v); return v; });\n`; | |
| } else { | |
| node.source += `export const ${value.name} = Promise.resolve(${value.value}).then(v => { return v; });\n`; | |
| } | |
| } else { | |
| node.source += `export const ${value.name} = ${value.value};\n`; | |
| if (!printless) node.source += `print("${name}: ${value.name} is", ${value.name});\n`; | |
| } | |
| } | |
| if (!printless) node.source += `print("${name} done");`; | |
| ++i; | |
| } | |
| console.log(`\x1b[2K\x1b[G${i}/${objects.length}\x1b[?25h`); | |
| return objects.map(node => node.source); | |
| } | |
| function connectEntry(objects) { | |
| for (let i = 1; i < objects.length; ++i) { | |
| if (objects[i].in.size == 0) { | |
| link(objects, 0, i); | |
| } | |
| } | |
| return objects; | |
| } | |
| function connectComponents(objects) { | |
| let components = findComponents(objects); | |
| while (components.length > 1) { | |
| const from = first(components[0]); | |
| const to = first(components[1]); | |
| link(objects, from.index, to.index); | |
| components = findComponents(objects); | |
| } | |
| return objects; | |
| } | |
| function addRandomLinks(objects, count) { | |
| if (objects.length < 2) { | |
| return objects; | |
| } | |
| for (let i = 0, j = 0; i < count; ++i, j += 2) { | |
| const indexA = rand(0, objects.length - 1); | |
| let indexB; | |
| do { | |
| indexB = rand(0, objects.length - 1); | |
| } while (indexB == indexA); | |
| link(objects, objects[indexA].index, objects[indexB].index); | |
| } | |
| return objects; | |
| } | |
| function findComponents(objects) { | |
| const pool = new Set(objects); | |
| const components = []; | |
| let length = objects.length; | |
| while (length > 0) { | |
| const start = first(pool); | |
| const component = new Set([start]); | |
| const visit = (node) => { | |
| component.add(node); | |
| pool.delete(node); | |
| --length; | |
| for (const set of [node.in, node.out]) { | |
| for (const neighbor of set) { | |
| if (!component.has(neighbor)) { | |
| visit(neighbor); | |
| } | |
| } | |
| } | |
| }; | |
| visit(start); | |
| components.push(component); | |
| } | |
| return components; | |
| } | |
| function link(objects, from, to) { | |
| objects[from].out.add(objects[to]); | |
| objects[to].in.add(objects[from]); | |
| return objects; | |
| } | |
| function makeObjects(count) { | |
| return [...Array(count)].map((x, index) => ({ | |
| index, | |
| in: new Set(), | |
| out: new Set(), | |
| hasTLA: Math.random() < tlaChance, | |
| source: "", | |
| valueCount: rand(1, maxValues), | |
| })); | |
| } | |
| function addCycle(objects, length) { | |
| const indices = shuffle([...Array(objects.length)].map((_, i) => i)).slice(0, length); | |
| for (let i = 0; i < length; ++i) { | |
| objects[indices[i]].out.append(objects[indices[(i + 1) % length]]); | |
| objects[indices[i]].in.append(objects[indices[(i + length - 1) % length]]); | |
| } | |
| return objects; | |
| } | |
| function shuffle(array) { | |
| for (let i = array.length - 1; i >= 1; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [array[i], array[j]] = [array[j], array[i]]; | |
| } | |
| return array; | |
| } | |
| function rand(min, max) { | |
| return min + Math.floor(Math.random() * (max - min + 1)); | |
| } | |
| function first(set) { | |
| return set.values().next().value; | |
| } | |
| function toDot(objects) { | |
| let out = "digraph G {\n"; | |
| for (const node of objects) { | |
| if (node.hasTLA) { | |
| out += `\t"${node.index}" [style=filled, fillcolor=green];\n`; | |
| } | |
| for (const neighbor of node.out) { | |
| out += `\t"${node.index}" -> "${neighbor.index}";\n`; | |
| } | |
| } | |
| return out + "}"; | |
| } | |
| function pathExists(objects, from, to) { | |
| const cache = objects.pathExistsCache || (objects.pathExistsCache = {}); | |
| if (typeof cache[from] === "undefined") { | |
| cache[from] = {}; | |
| } else if (to in cache[from]) { | |
| return cache[from][to]; | |
| } | |
| const visited = new Set(); | |
| const visit = node => { | |
| if (visited.has(node)) { | |
| return false; | |
| } | |
| visited.add(node); | |
| if (node.index == to) { | |
| return true; | |
| } | |
| for (const neighbor of node.out) { | |
| if (visit(neighbor)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| }; | |
| return cache[from][to] = visit(objects[from]); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment