Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b55e17b
Basic bot code!
tmr232 Dec 29, 2024
cf50af6
glibc
tmr232 Dec 29, 2024
39e696b
Make the bot utils usable from other languages
tmr232 Dec 30, 2024
2ec9823
Better outputs
tmr232 Dec 30, 2024
7fff603
Now with nicer rendering and better scan outputs!
tmr232 Dec 30, 2024
2618b70
More rendering options!
tmr232 Dec 30, 2024
7f4341d
Working, but very ugly version of the renderer!
tmr232 Dec 31, 2024
b18373d
Use prettier for formatting svelte files
tmr232 Dec 31, 2024
c81997d
Rename the render directory to render
tmr232 Dec 31, 2024
85bba33
Add todo items
tmr232 Dec 31, 2024
0166c88
Basic zooming capacity
tmr232 Dec 31, 2024
6be43f0
Make the SVG zoomable!
tmr232 Dec 31, 2024
5872e43
Publish the render website
tmr232 Dec 31, 2024
ba74470
Updated readme
tmr232 Dec 31, 2024
944e5dd
Updated changelog
tmr232 Dec 31, 2024
c832a30
Nicer title
tmr232 Dec 31, 2024
c6de3b6
Remove unused code
tmr232 Dec 31, 2024
5e3e11b
Fix toplevel async stuff
tmr232 Dec 31, 2024
ea4adf4
Lint
tmr232 Dec 31, 2024
66f7614
Add a button to open the code on Github
tmr232 Dec 31, 2024
5b2379c
Added utility to render a graph exported from Ghidra
tmr232 Jan 1, 2025
43046ab
Allow taking paths and not just URLs
tmr232 Jan 1, 2025
0ecad1e
Fix infinite loop and performance of backlink finding
v-p-b Jan 2, 2025
d4b5300
Fix typing
tmr232 Jan 3, 2025
644f37e
A bit of extra cleanup because I nitpick...
tmr232 Jan 3, 2025
9ac3f8c
Updated changelog
tmr232 Jan 3, 2025
b75ae1c
render page: added support for direct-from-graph rendering
tmr232 Jan 4, 2025
6d12f9b
Updates to scanning and rendering of functions
tmr232 Jan 4, 2025
5ab3da9
Support color schemes
tmr232 Jan 4, 2025
2f034a9
Support color schemes
tmr232 Jan 6, 2025
da1275f
remove atproto
tmr232 Jan 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
14 changes: 14 additions & 0 deletions scripts/cfg-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type Parser from "web-tree-sitter";
import { type CFG, mergeNodeAttrs } from "../src/control-flow/cfg-defs.ts";
import { type Language, newCFGBuilder } from "../src/control-flow/cfg.ts";
import { simplifyCFG, trimFor } from "../src/control-flow/graph-ops.ts";

export function buildCFG(func: Parser.SyntaxNode, language: Language): CFG {
const builder = newCFGBuilder(language, { flatSwitch: true });

let cfg = builder.buildCFG(func);

cfg = trimFor(cfg);
cfg = simplifyCFG(cfg, mergeNodeAttrs);
return cfg;
}
75 changes: 53 additions & 22 deletions scripts/render-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import * as path from "node:path";
import { parseArgs } from "node:util";
import { Graphviz } from "@hpcc-js/wasm-graphviz";
import type Parser from "web-tree-sitter";
import { type CFG, mergeNodeAttrs } from "../src/control-flow/cfg-defs.ts";
import type { SyntaxNode } from "web-tree-sitter";
import { type Language, supportedLanguages } from "../src/control-flow/cfg.ts";
import {
type Language,
newCFGBuilder,
supportedLanguages,
} from "../src/control-flow/cfg.ts";
import { simplifyCFG, trimFor } from "../src/control-flow/graph-ops.ts";
deserializeColorList,
getDarkColorList,
getLightColorList,
listToScheme,
} from "../src/control-flow/colors.ts";
import { graphToDot } from "../src/control-flow/render.ts";
import { getLanguage, iterFunctions } from "../src/file-parsing/bun.ts";
import { buildCFG } from "./cfg-helper.ts";

function isLanguage(language: string): language is Language {
return supportedLanguages.includes(language as Language);
Expand All @@ -24,20 +26,30 @@ function normalizeFuncdef(funcdef: string): string {
.trim();
}

function buildCFG(func: Parser.SyntaxNode, language: Language): CFG {
const builder = newCFGBuilder(language, { flatSwitch: true });

let cfg = builder.buildCFG(func);

cfg = trimFor(cfg);
cfg = simplifyCFG(cfg, mergeNodeAttrs);
return cfg;
export function getFuncDef(sourceCode: string, func: SyntaxNode): string {
const body = func.childForFieldName("body");
if (!body) {
throw new Error("No function body");
}
return normalizeFuncdef(sourceCode.slice(func.startIndex, body.startIndex));
}

function writeError(message: string): void {
Bun.write(Bun.stderr, `${message}\n`);
}

export async function getColorScheme(colors?: string) {
if (!colors || colors === "dark") {
return listToScheme(getDarkColorList());
}
if (colors === "light") {
return listToScheme(getLightColorList());
}
return colors
? listToScheme(deserializeColorList(await Bun.file(colors).text()))
: undefined;
}

async function main() {
process.on("SIGINT", () => {
// close watcher when Ctrl-C is pressed
Expand All @@ -58,6 +70,9 @@ async function main() {
out: {
type: "string",
},
colors: {
type: "string",
},
},
strict: true,
allowPositionals: true,
Expand All @@ -79,15 +94,26 @@ async function main() {

const possibleMatches: { name: string; func: Parser.SyntaxNode }[] = [];
const sourceCode = await Bun.file(filepath).text();
const startIndex = Number.parseInt(functionName);
let startPosition: { row: number; column: number } | undefined;
try {
startPosition = JSON.parse(functionName);
} catch {
startPosition = undefined;
}
for (const func of iterFunctions(sourceCode, language)) {
const body = func.childForFieldName("body");
if (!body) {
let funcDef: string;
try {
funcDef = getFuncDef(sourceCode, func);
} catch {
continue;
}
const funcDef = normalizeFuncdef(
sourceCode.slice(func.startIndex, body.startIndex),
);
if (funcDef.includes(functionName)) {
if (
funcDef.includes(functionName) ||
startIndex === func.startIndex ||
(startPosition?.row === func.startPosition.row &&
startPosition.column === func.startPosition.column)
) {
possibleMatches.push({ name: funcDef, func: func });
}
}
Expand All @@ -108,7 +134,10 @@ async function main() {
const func: Parser.SyntaxNode = possibleMatches[0].func;
const graphviz = await Graphviz.load();
const cfg = buildCFG(func, language);
const svg = graphviz.dot(graphToDot(cfg));

const colorScheme = await getColorScheme(values.colors);

const svg = graphviz.dot(graphToDot(cfg, false, undefined, colorScheme));

if (values.out) {
await Bun.write(values.out, svg);
Expand All @@ -117,4 +146,6 @@ async function main() {
}
}

await main();
if (require.main === module) {
await main();
}
13 changes: 10 additions & 3 deletions scripts/render-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ import type {
GraphNode,
} from "../src/control-flow/cfg-defs.ts";
import { graphToDot } from "../src/control-flow/render.ts";
import { getColorScheme } from "./render-function.ts";

async function main() {
const {
values,
positionals: [_runtime, _this, gist_url],
} = parseArgs({
args: Bun.argv,
strict: true,
allowPositionals: true,
options: {
colors: {
type: "string",
},
},
});

if (!gist_url) {
Expand All @@ -39,11 +46,11 @@ async function main() {
throw new Error("No entry found");
}
const cfg: CFG = { graph, entry, offsetToNode: [] };
const dot = graphToDot(cfg);
const colorScheme = await getColorScheme(values.colors);

const graphviz = await Graphviz.load();
const svg = graphviz.dot(dot);
const svg = graphviz.dot(graphToDot(cfg, false, undefined, colorScheme));
console.log(svg);
// console.log(dot);
}

if (require.main === module) {
Expand Down
105 changes: 91 additions & 14 deletions scripts/scan-codebase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,121 @@ import * as path from "node:path";
*/
import { parseArgs } from "node:util";
import { Glob } from "bun";
import { newCFGBuilder } from "../src/control-flow/cfg";
import {
fileTypes,
getLanguage,
iterFunctions,
} from "../src/file-parsing/bun.ts";
import { buildCFG } from "./cfg-helper.ts";
import { getFuncDef } from "./render-function.ts";

function iterSourceFiles(root: string): IterableIterator<string> {
export function iterSourceFiles(root: string): IterableIterator<string> {
const sourceGlob = new Glob(
`**/*.{${fileTypes.map(({ ext }) => ext).join(",")}}`,
);
return sourceGlob.scanSync(root);
}
function* iterFilenames(
root: string,
dirsToInclude: string[],
): IterableIterator<string> {
if (dirsToInclude.length === 1 && dirsToInclude[0] === "*") {
yield* iterSourceFiles(root);
} else {
for (const dir of dirsToInclude) {
for (const filename of iterSourceFiles(path.join(root, dir))) {
// We want the path relative to the root
yield path.join(dir, filename);
}
}
}
}

async function* iterFunctionInfo(
root: string,
filenames: IterableIterator<string>,
): AsyncIterableIterator<{
node_count: number;
start_position: { row: number; column: number };
funcdef: string;
filename: string;
}> {
for (const filename of filenames) {
const code = await Bun.file(path.join(root, filename)).text();
const language = getLanguage(filename);
for (const func of iterFunctions(code, language)) {
const cfg = buildCFG(func, language);
yield {
node_count: cfg.graph.order,
start_position: func.startPosition,
funcdef: getFuncDef(code, func),
filename: filename.replaceAll("\\", "/"),
};
}
}
}

async function generateIndex(
/** Project name on GitHub */
project: string,
/** Git ref */
ref: string,
/** Root on local filesystem */
root: string,
/** Directories to index, relative to the root */
dirsToInclude: string[],
) {
const filenames = iterFilenames(root, dirsToInclude);
const functions = await Array.fromAsync(iterFunctionInfo(root, filenames));
return {
version: 1,
content: {
index_type: "github",
project,
ref,
functions,
},
};
}

async function main() {
const { values } = parseArgs({
const {
values,
positionals: [_runtime, _this, ...dirsToInclude],
} = parseArgs({
args: Bun.argv,
options: {
project: {
type: "string",
},
ref: {
type: "string",
},
root: {
type: "string",
},
out: {
type: "string",
},
},
strict: true,
allowPositionals: true,
});

const root = values.root ?? ".";
if (!values.project || !values.ref || !values.root) {
throw new Error("Missing arguments");
}

for (const filename of iterSourceFiles(root)) {
const filepath = path.join(root, filename);
const code = await Bun.file(filepath).text();
const language = getLanguage(filename);
for (const func of iterFunctions(code, language)) {
const builder = newCFGBuilder(language, {});
const cfg = builder.buildCFG(func);
console.log(filepath, func.startPosition, cfg.graph.order);
}
const output = JSON.stringify(
await generateIndex(values.project, values.ref, values.root, dirsToInclude),
);
if (values.out) {
await Bun.write(values.out, output);
} else {
await Bun.write(Bun.stdout, output);
}
}

await main();
if (require.main === module) {
await main();
}
Loading