MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add upload based crash handler

+1762 -119
+21
docs/reports/.gitignore
··· 1 + # prod 2 + dist/ 3 + 4 + # deps 5 + node_modules/ 6 + .wrangler 7 + worker-configuration.d.ts 8 + 9 + # env 10 + .env 11 + .env.production 12 + .dev.vars 13 + 14 + # logs 15 + logs/ 16 + *.log 17 + npm-debug.log* 18 + yarn-debug.log* 19 + yarn-error.log* 20 + pnpm-debug.log* 21 + lerna-debug.log*
+12
docs/reports/.prettierrc.toml
··· 1 + printWidth = 100 2 + tabWidth = 2 3 + useTabs = false 4 + semi = true 5 + singleQuote = true 6 + quoteProps = "as-needed" 7 + jsxSingleQuote = false 8 + trailingComma = "all" 9 + bracketSpacing = true 10 + bracketSameLine = false 11 + arrowParens = "avoid" 12 + endOfLine = "lf"
docs/reports/ant.lockb

This is a binary file and will not be displayed.

+7
docs/reports/drizzle.config.ts
··· 1 + import { defineConfig } from 'drizzle-kit'; 2 + 3 + export default defineConfig({ 4 + schema: './src/schema.ts', 5 + out: './migrations', 6 + dialect: 'sqlite', 7 + });
+27
docs/reports/migrations/0000_conscious_vampiro.sql
··· 1 + CREATE TABLE `report_frames` ( 2 + `report_id` text NOT NULL, 3 + `frame_index` integer NOT NULL, 4 + `frame` text NOT NULL, 5 + PRIMARY KEY(`report_id`, `frame_index`), 6 + FOREIGN KEY (`report_id`) REFERENCES `reports`(`id`) ON UPDATE no action ON DELETE cascade 7 + ); 8 + --> statement-breakpoint 9 + CREATE TABLE `reports` ( 10 + `id` text PRIMARY KEY NOT NULL, 11 + `runtime` text NOT NULL, 12 + `version` text NOT NULL, 13 + `trace` text NOT NULL, 14 + `kind` text NOT NULL, 15 + `reason` text NOT NULL, 16 + `code` text NOT NULL, 17 + `target` text NOT NULL, 18 + `os` text NOT NULL, 19 + `arch` text NOT NULL, 20 + `fault_address` text NOT NULL, 21 + `elapsed_ms` integer, 22 + `peak_rss` integer, 23 + `first_seen_at` text NOT NULL, 24 + `last_seen_at` text NOT NULL, 25 + `hit_count` integer NOT NULL, 26 + `expires_at` text NOT NULL 27 + );
+200
docs/reports/migrations/meta/0000_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "74f454aa-26ab-4ce9-816d-b14eec7662d2", 5 + "prevId": "00000000-0000-0000-0000-000000000000", 6 + "tables": { 7 + "report_frames": { 8 + "name": "report_frames", 9 + "columns": { 10 + "report_id": { 11 + "name": "report_id", 12 + "type": "text", 13 + "primaryKey": false, 14 + "notNull": true, 15 + "autoincrement": false 16 + }, 17 + "frame_index": { 18 + "name": "frame_index", 19 + "type": "integer", 20 + "primaryKey": false, 21 + "notNull": true, 22 + "autoincrement": false 23 + }, 24 + "frame": { 25 + "name": "frame", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": true, 29 + "autoincrement": false 30 + } 31 + }, 32 + "indexes": {}, 33 + "foreignKeys": { 34 + "report_frames_report_id_reports_id_fk": { 35 + "name": "report_frames_report_id_reports_id_fk", 36 + "tableFrom": "report_frames", 37 + "tableTo": "reports", 38 + "columnsFrom": [ 39 + "report_id" 40 + ], 41 + "columnsTo": [ 42 + "id" 43 + ], 44 + "onDelete": "cascade", 45 + "onUpdate": "no action" 46 + } 47 + }, 48 + "compositePrimaryKeys": { 49 + "report_frames_report_id_frame_index_pk": { 50 + "columns": [ 51 + "report_id", 52 + "frame_index" 53 + ], 54 + "name": "report_frames_report_id_frame_index_pk" 55 + } 56 + }, 57 + "uniqueConstraints": {}, 58 + "checkConstraints": {} 59 + }, 60 + "reports": { 61 + "name": "reports", 62 + "columns": { 63 + "id": { 64 + "name": "id", 65 + "type": "text", 66 + "primaryKey": true, 67 + "notNull": true, 68 + "autoincrement": false 69 + }, 70 + "runtime": { 71 + "name": "runtime", 72 + "type": "text", 73 + "primaryKey": false, 74 + "notNull": true, 75 + "autoincrement": false 76 + }, 77 + "version": { 78 + "name": "version", 79 + "type": "text", 80 + "primaryKey": false, 81 + "notNull": true, 82 + "autoincrement": false 83 + }, 84 + "trace": { 85 + "name": "trace", 86 + "type": "text", 87 + "primaryKey": false, 88 + "notNull": true, 89 + "autoincrement": false 90 + }, 91 + "kind": { 92 + "name": "kind", 93 + "type": "text", 94 + "primaryKey": false, 95 + "notNull": true, 96 + "autoincrement": false 97 + }, 98 + "reason": { 99 + "name": "reason", 100 + "type": "text", 101 + "primaryKey": false, 102 + "notNull": true, 103 + "autoincrement": false 104 + }, 105 + "code": { 106 + "name": "code", 107 + "type": "text", 108 + "primaryKey": false, 109 + "notNull": true, 110 + "autoincrement": false 111 + }, 112 + "target": { 113 + "name": "target", 114 + "type": "text", 115 + "primaryKey": false, 116 + "notNull": true, 117 + "autoincrement": false 118 + }, 119 + "os": { 120 + "name": "os", 121 + "type": "text", 122 + "primaryKey": false, 123 + "notNull": true, 124 + "autoincrement": false 125 + }, 126 + "arch": { 127 + "name": "arch", 128 + "type": "text", 129 + "primaryKey": false, 130 + "notNull": true, 131 + "autoincrement": false 132 + }, 133 + "fault_address": { 134 + "name": "fault_address", 135 + "type": "text", 136 + "primaryKey": false, 137 + "notNull": true, 138 + "autoincrement": false 139 + }, 140 + "elapsed_ms": { 141 + "name": "elapsed_ms", 142 + "type": "integer", 143 + "primaryKey": false, 144 + "notNull": false, 145 + "autoincrement": false 146 + }, 147 + "peak_rss": { 148 + "name": "peak_rss", 149 + "type": "integer", 150 + "primaryKey": false, 151 + "notNull": false, 152 + "autoincrement": false 153 + }, 154 + "first_seen_at": { 155 + "name": "first_seen_at", 156 + "type": "text", 157 + "primaryKey": false, 158 + "notNull": true, 159 + "autoincrement": false 160 + }, 161 + "last_seen_at": { 162 + "name": "last_seen_at", 163 + "type": "text", 164 + "primaryKey": false, 165 + "notNull": true, 166 + "autoincrement": false 167 + }, 168 + "hit_count": { 169 + "name": "hit_count", 170 + "type": "integer", 171 + "primaryKey": false, 172 + "notNull": true, 173 + "autoincrement": false 174 + }, 175 + "expires_at": { 176 + "name": "expires_at", 177 + "type": "text", 178 + "primaryKey": false, 179 + "notNull": true, 180 + "autoincrement": false 181 + } 182 + }, 183 + "indexes": {}, 184 + "foreignKeys": {}, 185 + "compositePrimaryKeys": {}, 186 + "uniqueConstraints": {}, 187 + "checkConstraints": {} 188 + } 189 + }, 190 + "views": {}, 191 + "enums": {}, 192 + "_meta": { 193 + "schemas": {}, 194 + "tables": {}, 195 + "columns": {} 196 + }, 197 + "internal": { 198 + "indexes": {} 199 + } 200 + }
+13
docs/reports/migrations/meta/_journal.json
··· 1 + { 2 + "version": "7", 3 + "dialect": "sqlite", 4 + "entries": [ 5 + { 6 + "idx": 0, 7 + "version": "6", 8 + "when": 1777152332594, 9 + "tag": "0000_conscious_vampiro", 10 + "breakpoints": true 11 + } 12 + ] 13 + }
+30
docs/reports/package.json
··· 1 + { 2 + "name": "js.report", 3 + "type": "module", 4 + "scripts": { 5 + "dev": "wrangler dev", 6 + "deploy": "wrangler deploy --minify", 7 + "db:generate": "drizzle-kit generate --config drizzle.config.ts", 8 + "db:migrate": "wrangler d1 migrations apply js_report --remote", 9 + "cf-typegen": "wrangler types --env-interface CloudflareBindings" 10 + }, 11 + "dependencies": { 12 + "hono": "^4.12.15", 13 + "drizzle-orm": "^0.45.2", 14 + "short-uuid": "^6.0.3", 15 + "zod": "^4.3.6" 16 + }, 17 + "devDependencies": { 18 + "wrangler": "^4.4.0", 19 + "drizzle-kit": "^0.31.10" 20 + }, 21 + "trustedDependencies": [ 22 + "workerd", 23 + "sharp", 24 + "sha3", 25 + "keccak", 26 + "core-js", 27 + "secp256k1", 28 + "scrypt" 29 + ] 30 + }
docs/reports/public/assets/ant.png

This is a binary file and will not be displayed.

+103
docs/reports/public/assets/report.css
··· 1 + * { 2 + margin: 0; 3 + padding: 0; 4 + } 5 + 6 + html, 7 + code { 8 + font: 9 + 15px/22px arial, 10 + sans-serif; 11 + } 12 + 13 + html { 14 + background: #fff; 15 + color: #222; 16 + padding: 15px; 17 + } 18 + 19 + body { 20 + margin: 7% auto 0; 21 + max-width: 400px; 22 + min-height: 180px; 23 + padding: 30px 0 15px; 24 + } 25 + 26 + * > body { 27 + padding-right: 205px; 28 + } 29 + 30 + p { 31 + margin: 11px 0 22px; 32 + overflow: hidden; 33 + } 34 + 35 + ins { 36 + color: #777; 37 + text-decoration: none; 38 + } 39 + 40 + a { 41 + color: #15c; 42 + } 43 + 44 + code { 45 + background: #f8f8f8; 46 + border: 1px solid #eee; 47 + border-radius: 2px; 48 + padding: 0 3px; 49 + } 50 + 51 + ol { 52 + margin: 11px 0 22px 22px; 53 + } 54 + 55 + li { 56 + margin: 3px 0; 57 + overflow-wrap: anywhere; 58 + } 59 + 60 + #logo { 61 + width: 32px; 62 + height: 32px; 63 + margin-bottom: 11px; 64 + display: inline-block; 65 + user-select: none; 66 + pointer-events: none; 67 + } 68 + 69 + #logo img { 70 + width: 100%; 71 + height: 100%; 72 + } 73 + 74 + .meta { 75 + margin: 11px 0 22px; 76 + } 77 + 78 + .meta p { 79 + margin: 0; 80 + } 81 + 82 + .label { 83 + color: #777; 84 + } 85 + 86 + .frames code { 87 + background: transparent; 88 + border: 0; 89 + padding: 0; 90 + } 91 + 92 + .url { 93 + overflow-wrap: anywhere; 94 + } 95 + 96 + @media screen and (max-width: 772px) { 97 + body { 98 + background: none; 99 + margin-top: 0; 100 + max-width: none; 101 + padding-right: 0; 102 + } 103 + }
docs/reports/public/favicon.ico

This is a binary file and will not be displayed.

+135
docs/reports/src/index.ts
··· 1 + import { Hono } from 'hono'; 2 + import { asc, eq } from 'drizzle-orm'; 3 + import { drizzle } from 'drizzle-orm/d1'; 4 + import { renderBlank, renderReport } from './view'; 5 + import { generate as generateShortUuid } from 'short-uuid'; 6 + import { CrashReportSchema, reportFrames, reports, type CrashReport } from './schema'; 7 + 8 + type Bindings = { DB: D1Database }; 9 + const app = new Hono<{ Bindings: Bindings }>(); 10 + 11 + app.get('/', c => c.html(renderBlank(), 404)); 12 + 13 + async function reportTrace(report: CrashReport): Promise<string> { 14 + const traceInput = JSON.stringify({ 15 + runtime: report.runtime, 16 + version: report.version, 17 + target: report.target, 18 + os: report.os, 19 + arch: report.arch, 20 + code: report.code, 21 + reason: report.reason, 22 + addr: report.addr, 23 + frames: report.frames, 24 + }); 25 + 26 + const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(traceInput)); 27 + return [...new Uint8Array(digest)].map(byte => byte.toString(16).padStart(2, '0')).join(''); 28 + } 29 + 30 + function publicUrl(requestUrl: string, id: string): string { 31 + const url = new URL(requestUrl); 32 + return `${url.origin}/${id}`; 33 + } 34 + 35 + const reportFromRows = ( 36 + row: typeof reports.$inferSelect, 37 + frames: (typeof reportFrames.$inferSelect)[], 38 + ): CrashReport => ({ 39 + schema: 1, 40 + runtime: 'ant', 41 + version: row.version, 42 + target: row.target, 43 + os: row.os, 44 + arch: row.arch, 45 + kind: row.kind, 46 + code: row.code, 47 + reason: row.reason, 48 + addr: row.faultAddress, 49 + elapsedMs: row.elapsedMs, 50 + peakRss: row.peakRss, 51 + frames: frames.map(frame => frame.frame), 52 + }); 53 + 54 + async function insertReportFrames( 55 + db: ReturnType<typeof drizzle>, 56 + reportId: string, 57 + frames: string[], 58 + ): Promise<void> { 59 + if (!frames.length) return; 60 + await db 61 + .insert(reportFrames) 62 + .values(frames.map((frame, index) => ({ frame, reportId, frameIndex: index }))); 63 + } 64 + 65 + async function getReportFrames( 66 + db: ReturnType<typeof drizzle>, 67 + reportId: string, 68 + ): Promise<(typeof reportFrames.$inferSelect)[]> { 69 + return db 70 + .select() 71 + .from(reportFrames) 72 + .where(eq(reportFrames.reportId, reportId)) 73 + .orderBy(asc(reportFrames.frameIndex)); 74 + } 75 + 76 + app.get('/:id', async c => { 77 + const db = drizzle(c.env.DB); 78 + 79 + const [row] = await db 80 + .select() 81 + .from(reports) 82 + .where(eq(reports.id, c.req.param('id'))) 83 + .limit(1); 84 + 85 + if (!row) return c.html(renderBlank(), 404); 86 + const frames = await getReportFrames(db, row.id); 87 + 88 + return c.html(renderReport(reportFromRows(row, frames), publicUrl(c.req.url, row.id))); 89 + }); 90 + 91 + app.post('/report', async c => { 92 + let rawReport: unknown; 93 + try { 94 + rawReport = await c.req.json(); 95 + } catch { 96 + return c.json({ error: 'invalid payload' }, 400); 97 + } 98 + 99 + const parsedReport = CrashReportSchema.safeParse(rawReport); 100 + if (!parsedReport.success) return c.json({ error: 'invalid report' }, 400); 101 + 102 + const report = parsedReport.data; 103 + const trace = await reportTrace(report); 104 + 105 + const db = drizzle(c.env.DB); 106 + const id = generateShortUuid(); 107 + 108 + const now = new Date(); 109 + const expires = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000); 110 + 111 + await db.insert(reports).values({ 112 + id, 113 + trace, 114 + hitCount: 1, 115 + runtime: report.runtime, 116 + version: report.version, 117 + kind: report.kind, 118 + reason: report.reason, 119 + code: report.code, 120 + target: report.target, 121 + os: report.os, 122 + arch: report.arch, 123 + faultAddress: report.addr, 124 + elapsedMs: report.elapsedMs, 125 + peakRss: report.peakRss, 126 + firstSeenAt: now.toISOString(), 127 + lastSeenAt: now.toISOString(), 128 + expiresAt: expires.toISOString(), 129 + }); 130 + 131 + await insertReportFrames(db, id, report.frames); 132 + return c.text(publicUrl(c.req.url, id), 200); 133 + }); 134 + 135 + export default app;
+62
docs/reports/src/schema.ts
··· 1 + import { z } from 'zod'; 2 + import { integer, primaryKey, sqliteTable, text } from 'drizzle-orm/sqlite-core'; 3 + 4 + const boundedString = z 5 + .string() 6 + .min(1) 7 + .transform(value => value.slice(0, 512)); 8 + 9 + const boundedFrameString = z.string().transform(value => value.slice(0, 512)); 10 + const reportNumber = z.number().int().nonnegative().nullable(); 11 + 12 + export const CrashReportSchema = z 13 + .object({ 14 + schema: z.literal(1), 15 + runtime: z.literal('ant'), 16 + version: boundedString, 17 + target: boundedString, 18 + os: boundedString, 19 + arch: boundedString, 20 + kind: boundedString, 21 + code: boundedString, 22 + reason: boundedString, 23 + addr: boundedString, 24 + elapsedMs: reportNumber, 25 + peakRss: reportNumber, 26 + frames: z.array(boundedFrameString).transform(value => value.slice(0, 48)), 27 + }) 28 + .strict(); 29 + 30 + export const reports = sqliteTable('reports', { 31 + id: text('id').primaryKey(), 32 + runtime: text('runtime').notNull(), 33 + version: text('version').notNull(), 34 + trace: text('trace').notNull(), 35 + kind: text('kind').notNull(), 36 + reason: text('reason').notNull(), 37 + code: text('code').notNull(), 38 + target: text('target').notNull(), 39 + os: text('os').notNull(), 40 + arch: text('arch').notNull(), 41 + faultAddress: text('fault_address').notNull(), 42 + elapsedMs: integer('elapsed_ms'), 43 + peakRss: integer('peak_rss'), 44 + firstSeenAt: text('first_seen_at').notNull(), 45 + lastSeenAt: text('last_seen_at').notNull(), 46 + hitCount: integer('hit_count').notNull(), 47 + expiresAt: text('expires_at').notNull(), 48 + }); 49 + 50 + export const reportFrames = sqliteTable( 51 + 'report_frames', 52 + { 53 + reportId: text('report_id') 54 + .notNull() 55 + .references(() => reports.id, { onDelete: 'cascade' }), 56 + frameIndex: integer('frame_index').notNull(), 57 + frame: text('frame').notNull(), 58 + }, 59 + table => [primaryKey({ columns: [table.reportId, table.frameIndex] })], 60 + ); 61 + 62 + export type CrashReport = z.infer<typeof CrashReportSchema>;
+99
docs/reports/src/view.tsx
··· 1 + import type { Child } from 'hono/jsx'; 2 + import type { CrashReport } from './schema'; 3 + 4 + function formatBytes(value: number | null): string { 5 + if (!value || !Number.isFinite(value)) return 'unknown'; 6 + if (value >= 1024 * 1024) return `${Math.round(value / 1024 / 1024)}mb`; 7 + if (value >= 1024) return `${Math.round(value / 1024)}kb`; 8 + return `${value}b`; 9 + } 10 + 11 + const Shell = ({ title, children }: { title: string; children: Child }) => ( 12 + <html lang="en"> 13 + <head> 14 + <meta charset="utf-8" /> 15 + <meta name="viewport" content="width=device-width,initial-scale=1" /> 16 + <link rel="icon" type="image/x-icon" href="/favicon.ico" /> 17 + <link rel="stylesheet" href="/assets/report.css" /> 18 + <title>{title}</title> 19 + </head> 20 + <body>{children}</body> 21 + </html> 22 + ); 23 + 24 + const Logo = () => ( 25 + <a href="/" id="logo"> 26 + <img src="/assets/ant.png" alt="Home" /> 27 + </a> 28 + ); 29 + 30 + const BlankPage = () => ( 31 + <Shell title="js.report"> 32 + <Logo /> 33 + <p> 34 + <b>404.</b> <ins>That's an error.</ins> 35 + </p> 36 + <p>If you were given a report link, check the URL and try again.</p> 37 + </Shell> 38 + ); 39 + 40 + const ReportPage = ({ report, url }: { report: CrashReport; url: string }) => ( 41 + <Shell title={`${report.reason} at ${report.addr}`}> 42 + <Logo /> 43 + <p> 44 + <b>{report.reason}.</b>{' '} 45 + <ins> 46 + {report.code} at {report.addr} 47 + </ins> 48 + <br /> 49 + <span>Ant crashed and sent a redacted report.</span> 50 + </p> 51 + 52 + <div class="meta"> 53 + <p> 54 + <span class="label">Runtime:</span> Ant {report.version} 55 + </p> 56 + <p> 57 + <span class="label">Platform:</span> {report.os} {report.arch} 58 + </p> 59 + <p> 60 + <span class="label">Target:</span> <code>{report.target}</code> 61 + </p> 62 + <p> 63 + <span class="label">Elapsed:</span> {report.elapsedMs ?? 'unknown'}ms 64 + </p> 65 + <p> 66 + <span class="label">Peak RSS:</span> {formatBytes(report.peakRss)} 67 + </p> 68 + </div> 69 + 70 + <div class="meta"> 71 + <i>Native backtrace:</i> 72 + <ol class="frames"> 73 + {report.frames.length ? ( 74 + report.frames.map(frame => ( 75 + <li> 76 + <code>{frame}</code> 77 + </li> 78 + )) 79 + ) : ( 80 + <li> 81 + <ins>No native frames were captured.</ins> 82 + </li> 83 + )} 84 + </ol> 85 + </div> 86 + 87 + <p class="url"> 88 + <span class="label">This report URL:</span> <a href={url}>{url}</a> 89 + </p> 90 + </Shell> 91 + ); 92 + 93 + export function renderBlank(): string { 94 + return `<!doctype html>${(<BlankPage />)}`; 95 + } 96 + 97 + export function renderReport(report: CrashReport, url: string): string { 98 + return `<!doctype html>${(<ReportPage report={report} url={url} />)}`; 99 + }
+12
docs/reports/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ESNext", 4 + "module": "ESNext", 5 + "moduleResolution": "Bundler", 6 + "strict": true, 7 + "skipLibCheck": true, 8 + "lib": ["ESNext"], 9 + "jsx": "react-jsx", 10 + "jsxImportSource": "hono/jsx" 11 + } 12 + }
+23
docs/reports/wrangler.toml
··· 1 + name = "js-report" 2 + main = "src/index.ts" 3 + 4 + workers_dev = false 5 + compatibility_date = "2026-04-25" 6 + compatibility_flags = ["nodejs_compat"] 7 + 8 + [assets] 9 + directory = "./public" 10 + binding = "ASSETS" 11 + 12 + [observability] 13 + enabled = true 14 + 15 + [[d1_databases]] 16 + binding = "DB" 17 + database_name = "js_report" 18 + database_id = "aed7ba56-b1f0-41f0-ad77-e4ef981aad4f" 19 + remote = true 20 + 21 + [[routes]] 22 + pattern = "js.report" 23 + custom_domain = true
+8 -1
include/crash.h
··· 1 1 #ifndef ANT_CRASH_H 2 2 #define ANT_CRASH_H 3 3 4 - void ant_crash_init(void); 4 + #include <stdbool.h> 5 + #include "types.h" 6 + 7 + void ant_crash_init(int argc, char **argv); 8 + void ant_crash_suppress_reporting(void); 9 + 10 + int ant_crash_run_internal_report(ant_t *js); 11 + bool ant_crash_is_internal_report(int argc, char **argv); 5 12 6 13 #endif
+3
include/modules/fetch.h
··· 1 1 #ifndef FETCH_H 2 2 #define FETCH_H 3 3 4 + #include "types.h" 5 + 4 6 void init_fetch_module(void); 5 7 int has_pending_fetches(void); 8 + ant_value_t ant_fetch(ant_params_t); 6 9 7 10 #endif
+985 -114
src/crash.c
··· 1 1 #include <compat.h> // IWYU pragma: keep 2 - #include "crash.h" 3 2 3 + #include <stdbool.h> 4 + #include <stdint.h> 5 + #include <signal.h> 4 6 #include <stdio.h> 5 - #include <stdint.h> 6 7 #include <stdlib.h> 7 8 #include <string.h> 9 + #include <time.h> 10 + #include <crprintf.h> 8 11 9 - #ifndef _WIN32 10 - #include <signal.h> 12 + #include "ant.h" 13 + #include "crash.h" 14 + #include "internal.h" 15 + #include "reactor.h" 16 + 17 + #include "silver/engine.h" 18 + #include "modules/assert.h" 19 + #include "modules/fetch.h" 20 + #include "modules/json.h" 21 + 22 + #define ANT_CRASH_FRAME_MAX 24 23 + #define ANT_CRASH_ALT_STACK_SIZE 65536 24 + #define ANT_CRASH_ARGV_MAX 1024 25 + #define ANT_CRASH_EXE_PATH_MAX 1024 26 + #define ANT_CRASH_PAYLOAD_MAX 32768 27 + 28 + static char crash_argv[ANT_CRASH_ARGV_MAX] = ""; 29 + static char crash_exe_path[ANT_CRASH_EXE_PATH_MAX] = ""; 30 + 31 + static bool crash_print_trace = false; 32 + static bool crash_reporting_enabled = true; 33 + static bool crash_report_status_printed = false; 34 + static bool crash_report_status_inline = false; 35 + 36 + static uint64_t crash_start_ms = 0; 37 + static volatile sig_atomic_t crash_reporting_suppressed = 0; 38 + 39 + static bool should_upload_report(void) { 40 + return 41 + crash_reporting_enabled && 42 + crash_reporting_suppressed == 0; 43 + } 44 + 45 + bool ant_crash_is_internal_report(int argc, char **argv) { 46 + return 47 + argc >= 2 && argv && 48 + argv[1] && strcmp(argv[1], "__internal-crash-report") == 0; 49 + } 50 + 51 + #ifdef _WIN32 52 + #include <dbghelp.h> 53 + #include <io.h> 54 + #else 55 + #include <limits.h> 56 + #include <dlfcn.h> 57 + #include <fcntl.h> 11 58 #include <unistd.h> 59 + #include <sys/resource.h> 60 + #include <sys/wait.h> 12 61 13 - #define ANT_CRASH_ALT_STACK_SIZE (64 * 1024) 62 + #ifdef __APPLE__ 63 + #include <mach-o/dyld.h> 64 + #endif 14 65 15 66 #if defined(__APPLE__) || defined(__linux__) || defined(__GLIBC__) 16 67 #define ANT_CRASH_HAVE_EXECINFO 1 17 68 #include <execinfo.h> 18 69 #endif 70 + #endif 19 71 20 - static void as_write(int fd, const char *s) { 21 - size_t len = 0; 22 - while (s[len]) len++; 23 - ssize_t _ = write(fd, s, len); 24 - (void)_; 72 + typedef struct { 73 + char *buf; 74 + size_t cap; 75 + size_t len; 76 + } crash_buf_t; 77 + 78 + static void cb_putc(crash_buf_t *b, char c) { 79 + if (b->len + 1 >= b->cap) return; 80 + b->buf[b->len++] = c; 81 + b->buf[b->len] = '\0'; 25 82 } 26 83 27 - static void as_write_uint(int fd, unsigned long v) { 28 - char buf[32]; 29 - int i = (int)sizeof(buf); 30 - buf[--i] = '\0'; 31 - if (v == 0) buf[--i] = '0'; 84 + static void cb_puts(crash_buf_t *b, const char *s) { 85 + if (!s) return; 86 + while (*s && b->len + 1 < b->cap) b->buf[b->len++] = *s++; 87 + if (b->cap) b->buf[b->len] = '\0'; 88 + } 89 + 90 + static void cb_put_uint(crash_buf_t *b, unsigned long long v) { 91 + char tmp[32]; 92 + int i = (int)sizeof(tmp); 93 + tmp[--i] = '\0'; 94 + if (v == 0) tmp[--i] = '0'; 32 95 while (v && i > 0) { 33 - buf[--i] = (char)('0' + (v % 10)); 96 + tmp[--i] = (char)('0' + (v % 10)); 34 97 v /= 10; 35 98 } 36 - as_write(fd, &buf[i]); 99 + cb_puts(b, &tmp[i]); 100 + } 101 + 102 + static void cb_put_hex(crash_buf_t *b, uint64_t v) { 103 + char tmp[32]; 104 + int i = (int)sizeof(tmp); 105 + tmp[--i] = '\0'; 106 + if (v == 0) tmp[--i] = '0'; 107 + while (v && i > 0) { 108 + unsigned d = (unsigned)(v & 0xf); 109 + tmp[--i] = (char)(d < 10 ? '0' + d : 'a' + d - 10); 110 + v >>= 4; 111 + } 112 + cb_puts(b, "0x"); 113 + cb_puts(b, &tmp[i]); 114 + } 115 + 116 + static void cb_put_json_string(crash_buf_t *b, const char *s) { 117 + static const char hexdigits[] = "0123456789abcdef"; 118 + cb_putc(b, '"'); 119 + 120 + if (s) for (const unsigned char *p = (const unsigned char *)s; *p; p++) { 121 + switch (*p) { 122 + case '"': cb_puts(b, "\\\""); break; 123 + case '\\': cb_puts(b, "\\\\"); break; 124 + case '\b': cb_puts(b, "\\b"); break; 125 + case '\f': cb_puts(b, "\\f"); break; 126 + case '\n': cb_puts(b, "\\n"); break; 127 + case '\r': cb_puts(b, "\\r"); break; 128 + case '\t': cb_puts(b, "\\t"); break; 129 + default: if (*p < 0x20) { 130 + cb_puts(b, "\\u00"); 131 + cb_putc(b, hexdigits[*p >> 4]); 132 + cb_putc(b, hexdigits[*p & 0xf]); 133 + } else cb_putc(b, (char)*p); 134 + }} 135 + 136 + cb_putc(b, '"'); 137 + } 138 + 139 + static bool env_bool(const char *value, bool default_value) { 140 + if (!value || !*value) return default_value; 141 + if ( 142 + strcmp(value, "0") == 0 || 143 + strcmp(value, "false") == 0 || 144 + strcmp(value, "FALSE") == 0 || 145 + strcmp(value, "off") == 0 || 146 + strcmp(value, "OFF") == 0 || 147 + strcmp(value, "no") == 0 || 148 + strcmp(value, "NO") == 0 149 + ) return false; 150 + return true; 151 + } 152 + 153 + static const char *path_basename_const(const char *path) { 154 + if (!path) return ""; 155 + const char *base = path; 156 + for (const char *p = path; *p; p++) { 157 + if (*p == '/' || *p == '\\') base = p + 1; 158 + } 159 + return base; 160 + } 161 + 162 + static void init_argv_strings(int argc, char **argv) { 163 + crash_buf_t payload = { crash_argv, sizeof(crash_argv), 0 }; 164 + int limit = argc < 8 ? argc : 8; 165 + for (int i = 0; i < limit; i++) { 166 + if (i) cb_putc(&payload, ','); 167 + cb_put_json_string(&payload, argv[i] ? path_basename_const(argv[i]) : ""); 168 + } 169 + if (argc > limit) { 170 + if (limit > 0) cb_putc(&payload, ','); 171 + cb_put_json_string(&payload, "..."); 172 + } 173 + } 174 + 175 + static void init_report_controls() { 176 + crash_print_trace = env_bool(getenv("ANT_CRASH_TRACE"), false); 177 + const char *enabled = getenv("ANT_ENABLE_CRASH_REPORTING"); 178 + if (enabled) crash_reporting_enabled = env_bool(enabled, true); 179 + else crash_reporting_enabled = !env_bool(getenv("DO_NOT_TRACK"), false); 180 + if (getenv("ANT_CRASH_REPORT_URL") && !enabled) crash_reporting_enabled = true; 181 + } 182 + 183 + static void init_exe_path(int argc, char **argv) { 184 + #ifdef _WIN32 185 + DWORD len = GetModuleFileNameA(NULL, crash_exe_path, (DWORD)sizeof(crash_exe_path)); 186 + if (len > 0 && len < sizeof(crash_exe_path)) return; 187 + #else 188 + #ifdef __APPLE__ 189 + uint32_t size = (uint32_t)sizeof(crash_exe_path); 190 + char tmp[ANT_CRASH_EXE_PATH_MAX]; 191 + if (_NSGetExecutablePath(tmp, &size) == 0) { 192 + char *resolved = realpath(tmp, crash_exe_path); 193 + if (resolved) return; 194 + strncpy(crash_exe_path, tmp, sizeof(crash_exe_path) - 1); 195 + crash_exe_path[sizeof(crash_exe_path) - 1] = '\0'; 196 + return; 197 + } 198 + #elif defined(__linux__) 199 + ssize_t len = readlink("/proc/self/exe", crash_exe_path, sizeof(crash_exe_path) - 1); 200 + if (len > 0) { 201 + crash_exe_path[len] = '\0'; 202 + return; 203 + } 204 + #endif 205 + #endif 206 + if (argc > 0 && argv && argv[0]) { 207 + strncpy(crash_exe_path, argv[0], sizeof(crash_exe_path) - 1); 208 + crash_exe_path[sizeof(crash_exe_path) - 1] = '\0'; 209 + } 210 + } 211 + 212 + static const char *os_name(void) { 213 + #ifdef _WIN32 214 + return "Windows"; 215 + #elif defined(__APPLE__) 216 + return "macOS"; 217 + #elif defined(__linux__) 218 + return "Linux"; 219 + #else 220 + return "unknown-os"; 221 + #endif 222 + } 223 + 224 + static const char *arch_name(void) { 225 + #if defined(__aarch64__) || defined(_M_ARM64) 226 + return "arm64"; 227 + #elif defined(__x86_64__) || defined(_M_X64) 228 + return "x64"; 229 + #elif defined(__i386__) || defined(_M_IX86) 230 + return "x86"; 231 + #else 232 + return "unknown-arch"; 233 + #endif 234 + } 235 + 236 + static uint64_t now_ms(void) { 237 + #ifdef _WIN32 238 + return GetTickCount64(); 239 + #else 240 + struct timespec ts; 241 + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return 0; 242 + return (uint64_t)ts.tv_sec * 1000ULL + (uint64_t)ts.tv_nsec / 1000000ULL; 243 + #endif 244 + } 245 + 246 + static unsigned long long peak_rss_bytes(void) { 247 + #ifdef _WIN32 248 + return 0; 249 + #else 250 + struct rusage ru; 251 + if (getrusage(RUSAGE_SELF, &ru) != 0) return 0; 252 + #ifdef __APPLE__ 253 + return (unsigned long long)ru.ru_maxrss; 254 + #else 255 + return (unsigned long long)ru.ru_maxrss * 1024ULL; 256 + #endif 257 + #endif 258 + } 259 + 260 + static unsigned long long crash_process_id(void) { 261 + #ifdef _WIN32 262 + return (unsigned long long)GetCurrentProcessId(); 263 + #else 264 + return (unsigned long long)getpid(); 265 + #endif 266 + } 267 + 268 + static bool crash_streq_len(const char *s, size_t len, const char *literal) { 269 + size_t literal_len = strlen(literal); 270 + return len == literal_len && strncmp(s, literal, literal_len) == 0; 271 + } 272 + 273 + static const char *crash_code_detail(const char *code, size_t len) { 274 + if (crash_streq_len(code, len, "SIGSEGV")) return "invalid memory access"; 275 + if (crash_streq_len(code, len, "SIGBUS")) return "bus error"; 276 + if (crash_streq_len(code, len, "SIGFPE")) return "floating point exception"; 277 + if (crash_streq_len(code, len, "SIGILL")) return "illegal instruction"; 278 + if (crash_streq_len(code, len, "SIGABRT")) return "abort"; 279 + 280 + if (crash_streq_len(code, len, "EXCEPTION_ACCESS_VIOLATION")) return "invalid memory access"; 281 + if (crash_streq_len(code, len, "EXCEPTION_STACK_OVERFLOW")) return "stack overflow"; 282 + if (crash_streq_len(code, len, "EXCEPTION_ILLEGAL_INSTRUCTION")) return "illegal instruction"; 283 + 284 + return "fatal error"; 285 + } 286 + 287 + static int crash_code_signal_number(const char *code, size_t len) { 288 + #ifdef SIGSEGV 289 + if (crash_streq_len(code, len, "SIGSEGV")) return SIGSEGV; 290 + #endif 291 + #ifdef SIGBUS 292 + if (crash_streq_len(code, len, "SIGBUS")) return SIGBUS; 293 + #endif 294 + #ifdef SIGFPE 295 + if (crash_streq_len(code, len, "SIGFPE")) return SIGFPE; 296 + #endif 297 + #ifdef SIGILL 298 + if (crash_streq_len(code, len, "SIGILL")) return SIGILL; 299 + #endif 300 + #ifdef SIGABRT 301 + if (crash_streq_len(code, len, "SIGABRT")) return SIGABRT; 302 + #endif 303 + return 0; 304 + } 305 + 306 + static const char *posix_signal_reason(int sig) { 307 + switch (sig) { 308 + case SIGSEGV: return "Segmentation fault"; 309 + #ifdef SIGBUS 310 + case SIGBUS: return "Bus error"; 311 + #endif 312 + case SIGFPE: return "Floating point exception"; 313 + case SIGILL: return "Illegal instruction"; 314 + case SIGABRT: return "Abort"; 315 + default: return "Fatal signal"; 316 + }} 317 + 318 + static const char *posix_signal_code(int sig) { 319 + switch (sig) { 320 + case SIGSEGV: return "SIGSEGV"; 321 + #ifdef SIGBUS 322 + case SIGBUS: return "SIGBUS"; 323 + #endif 324 + case SIGFPE: return "SIGFPE"; 325 + case SIGILL: return "SIGILL"; 326 + case SIGABRT: return "SIGABRT"; 327 + default: return "SIGNAL"; 328 + }} 329 + 330 + static void format_native_frame(char *out, size_t out_cap, uintptr_t addr) { 331 + if (out_cap == 0) return; 332 + out[0] = '\0'; 333 + 334 + #ifdef _WIN32 335 + HANDLE process = GetCurrentProcess(); 336 + DWORD64 displacement = 0; 337 + DWORD64 base = SymGetModuleBase64(process, (DWORD64)addr); 338 + 339 + char module_path[MAX_PATH] = ""; 340 + const char *image = "unknown"; 341 + if (base && GetModuleFileNameA((HMODULE)(uintptr_t)base, module_path, (DWORD)sizeof(module_path)) > 0) 342 + image = path_basename_const(module_path); 343 + 344 + // TODO: make dry 345 + union { 346 + SYMBOL_INFO info; 347 + char storage[sizeof(SYMBOL_INFO) + MAX_SYM_NAME]; 348 + } symbol_buf; 349 + 350 + SYMBOL_INFO *symbol = &symbol_buf.info; 351 + memset(&symbol_buf, 0, sizeof(symbol_buf)); 352 + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); 353 + symbol->MaxNameLen = MAX_SYM_NAME; 354 + 355 + if (SymFromAddr(process, (DWORD64)addr, &displacement, symbol)) { 356 + snprintf( 357 + out, out_cap, "%s 0x%016llx %s + %llu", image, 358 + (unsigned long long)addr, symbol->Name, (unsigned long long)displacement); 359 + return; 360 + } 361 + 362 + snprintf(out, out_cap, "%s 0x%016llx", image, (unsigned long long)addr); 363 + #else 364 + Dl_info info; 365 + memset(&info, 0, sizeof(info)); 366 + 367 + if (dladdr((void *)addr, &info) && info.dli_fname) { 368 + const char *image = path_basename_const(info.dli_fname); 369 + if (info.dli_sname && info.dli_saddr) { 370 + unsigned long long offset = (unsigned long long)(addr - (uintptr_t)info.dli_saddr); 371 + snprintf( 372 + out, out_cap, "%s 0x%016llx %s + %llu", 373 + image, (unsigned long long)addr, info.dli_sname, offset); 374 + return; 375 + } 376 + if (info.dli_fbase) { 377 + unsigned long long offset = (unsigned long long)(addr - (uintptr_t)info.dli_fbase); 378 + snprintf(out, out_cap, "%s 0x%016llx + %llu", image, (unsigned long long)addr, offset); 379 + return; 380 + } 381 + snprintf(out, out_cap, "%s 0x%016llx", image, (unsigned long long)addr); 382 + return; 383 + } 384 + 385 + snprintf(out, out_cap, "0x%016llx", (unsigned long long)addr); 386 + #endif 387 + } 388 + 389 + static size_t build_report_payload( 390 + char *payload_buf, size_t payload_cap, 391 + const char *kind, const char *code, const char *reason, 392 + uint64_t fault_addr, const uintptr_t *frames, int frame_count 393 + ) { 394 + crash_buf_t p = { payload_buf, payload_cap, 0 }; 395 + cb_puts(&p, "{\"upload\":"); 396 + cb_puts(&p, should_upload_report() ? "true" : "false"); 397 + cb_puts(&p, ",\"trace\":"); 398 + cb_puts(&p, crash_print_trace ? "true" : "false"); 399 + cb_puts(&p, ",\"pid\":"); 400 + cb_put_uint(&p, crash_process_id()); 401 + cb_puts(&p, ",\"argv\":["); 402 + cb_puts(&p, crash_argv); 403 + cb_puts(&p, "],\"report\":{\"schema\":1,\"runtime\":\"ant\",\"version\":"); 404 + cb_put_json_string(&p, ANT_VERSION); 405 + cb_puts(&p, ",\"target\":"); 406 + cb_put_json_string(&p, ANT_TARGET_TRIPLE); 407 + cb_puts(&p, ",\"os\":"); 408 + cb_put_json_string(&p, os_name()); 409 + cb_puts(&p, ",\"arch\":"); 410 + cb_put_json_string(&p, arch_name()); 411 + cb_puts(&p, ",\"kind\":"); 412 + cb_put_json_string(&p, kind); 413 + cb_puts(&p, ",\"code\":"); 414 + cb_put_json_string(&p, code); 415 + cb_puts(&p, ",\"reason\":"); 416 + cb_put_json_string(&p, reason); 417 + cb_puts(&p, ",\"addr\":"); 418 + cb_putc(&p, '"'); 419 + cb_put_hex(&p, fault_addr); 420 + cb_putc(&p, '"'); 421 + cb_puts(&p, ",\"elapsedMs\":"); 422 + cb_put_uint(&p, now_ms() - crash_start_ms); 423 + cb_puts(&p, ",\"peakRss\":"); 424 + cb_put_uint(&p, peak_rss_bytes()); 425 + cb_puts(&p, ",\"frames\":["); 426 + for (int i = 0; i < frame_count && i < ANT_CRASH_FRAME_MAX; i++) { 427 + if (i) cb_putc(&p, ','); 428 + char frame_text[384]; 429 + format_native_frame(frame_text, sizeof(frame_text), frames[i]); 430 + cb_put_json_string(&p, frame_text); 431 + } 432 + cb_puts(&p, "]}}"); 433 + 434 + return p.len; 435 + } 436 + 437 + static bool crash_stderr_is_tty(void) { 438 + #ifdef _WIN32 439 + return _isatty(_fileno(stderr)) != 0; 440 + #else 441 + return isatty(fileno(stderr)); 442 + #endif 443 + } 444 + 445 + #ifndef _WIN32 446 + static void write_all_fd(int fd, const char *data, size_t len) { 447 + while (len > 0) { 448 + ssize_t n = write(fd, data, len); 449 + if (n <= 0) return; 450 + data += n; 451 + len -= (size_t)n; 452 + }} 453 + 454 + static void spawn_reporter(const char *payload, size_t payload_len) { 455 + int stdin_pipe[2]; 456 + if (pipe(stdin_pipe) != 0) return; 457 + 458 + pid_t pid = fork(); 459 + if (pid != 0) { 460 + close(stdin_pipe[0]); 461 + if (pid > 0) write_all_fd(stdin_pipe[1], payload, payload_len); 462 + close(stdin_pipe[1]); 463 + if (pid > 0) { 464 + int status = 0; 465 + (void)waitpid(pid, &status, 0); 466 + } 467 + return; 468 + } 469 + 470 + close(stdin_pipe[1]); 471 + dup2(stdin_pipe[0], STDIN_FILENO); 472 + if (stdin_pipe[0] > STDERR_FILENO) close(stdin_pipe[0]); 473 + 474 + int null_out = open("/dev/null", O_WRONLY); 475 + if (null_out >= 0) { 476 + dup2(null_out, STDOUT_FILENO); 477 + if (null_out > STDERR_FILENO) close(null_out); 478 + } 479 + 480 + const char *exe = crash_exe_path[0] ? crash_exe_path : "ant"; 481 + char *const reporter_argv[] = { 482 + (char *)exe, 483 + "__internal-crash-report", 484 + NULL 485 + }; 486 + 487 + execv(exe, reporter_argv); 488 + execvp(exe, reporter_argv); 489 + _exit(0); 490 + } 491 + #else 492 + static void spawn_reporter(const char *payload, size_t payload_len) { 493 + if (!crash_exe_path[0]) return; 494 + 495 + SECURITY_ATTRIBUTES sa; 496 + memset(&sa, 0, sizeof(sa)); 497 + sa.nLength = sizeof(sa); 498 + sa.bInheritHandle = TRUE; 499 + 500 + HANDLE read_pipe = NULL; 501 + HANDLE write_pipe = NULL; 502 + if (!CreatePipe(&read_pipe, &write_pipe, &sa, 0)) return; 503 + SetHandleInformation(write_pipe, HANDLE_FLAG_INHERIT, 0); 504 + 505 + HANDLE null_out = CreateFileA("NUL", GENERIC_WRITE, FILE_SHARE_WRITE, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 506 + 507 + char cmd[ANT_CRASH_EXE_PATH_MAX + 64] = ""; 508 + snprintf(cmd, sizeof(cmd), "\"%s\" __internal-crash-report", crash_exe_path); 509 + 510 + STARTUPINFOA si; 511 + PROCESS_INFORMATION pi; 512 + memset(&si, 0, sizeof(si)); 513 + memset(&pi, 0, sizeof(pi)); 514 + si.cb = sizeof(si); 515 + si.dwFlags = STARTF_USESTDHANDLES; 516 + si.hStdInput = read_pipe; 517 + si.hStdOutput = null_out != INVALID_HANDLE_VALUE ? null_out : GetStdHandle(STD_OUTPUT_HANDLE); 518 + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); 519 + 520 + if (CreateProcessA(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { 521 + CloseHandle(read_pipe); 522 + DWORD written = 0; 523 + WriteFile(write_pipe, payload, (DWORD)payload_len, &written, NULL); 524 + CloseHandle(write_pipe); 525 + WaitForSingleObject(pi.hProcess, INFINITE); 526 + CloseHandle(pi.hThread); 527 + CloseHandle(pi.hProcess); 528 + } else { 529 + CloseHandle(read_pipe); 530 + CloseHandle(write_pipe); 531 + } 532 + if (null_out != INVALID_HANDLE_VALUE) CloseHandle(null_out); 533 + } 534 + #endif 535 + 536 + static ant_value_t crash_report_noop(ant_t *js, ant_value_t *args, int nargs) { 537 + if (crash_report_status_printed) return js_mkundef(); 538 + crash_report_status_printed = true; 539 + 540 + // TODO: make dry 541 + if (crash_report_status_inline) crfprintf(stderr, "\r\033[2K <red>Crash report upload failed.</red>\n"); 542 + else crfprintf(stderr, "<red>Crash report upload failed.</red>\n"); 543 + 544 + if (args && nargs > 0) { 545 + const char *message = js_str(js, args[0]); 546 + if (message && *message) { 547 + fputs(message, stderr); 548 + fputc('\n', stderr); 549 + }} 550 + 551 + return js_mkundef(); 552 + } 553 + 554 + static ant_value_t crash_report_print_url(ant_t *js, ant_value_t *args, int nargs) { 555 + if (nargs < 1) return js_mkundef(); 556 + 557 + size_t len = 0; 558 + const char *s = vtype(args[0]) == T_STR ? js_getstr(js, args[0], &len) : NULL; 559 + 560 + if (!s || len == 0) { 561 + if (!crash_report_status_printed) { 562 + crash_report_status_printed = true; 563 + // TODO: make dry 564 + if (crash_report_status_inline) crfprintf(stderr, "\r\033[2K <red>Crash report upload failed.</red>\n"); 565 + else crfprintf(stderr, "<red>Crash report upload failed.</red>\n"); 566 + } 567 + return js_mkundef(); 568 + } 569 + 570 + crash_report_status_printed = true; 571 + if (crash_report_status_inline) crfprintf(stderr, "\r\033[2K <cyan>%.*s</cyan>\n\n", (int)len, s); 572 + else crfprintf(stderr, "\n <cyan>%.*s</cyan>\n\n", (int)len, s); 573 + 574 + return args[0]; 575 + } 576 + 577 + static ant_value_t crash_report_response_text(ant_t *js, ant_value_t *args, int nargs) { 578 + if (nargs < 1) return js_mkundef(); 579 + 580 + ant_value_t text_fn = js_getprop_fallback(js, args[0], "text"); 581 + if (!is_callable(text_fn)) { 582 + if (!crash_report_status_printed) { 583 + crash_report_status_printed = true; 584 + // TODO: make dry 585 + if (crash_report_status_inline) crfprintf(stderr, "\r\033[2K <red>Crash report upload failed.</red>\n"); 586 + else crfprintf(stderr, "<red>Crash report upload failed.</red>\n"); 587 + fputs("Response.text is not available.\n", stderr); 588 + } 589 + return js_mkundef(); 590 + } 591 + 592 + ant_value_t text_promise = sv_vm_call(js->vm, js, text_fn, args[0], NULL, 0, NULL, false); 593 + if (is_err(text_promise)) return text_promise; 594 + 595 + ant_value_t print_promise = js_promise_then( 596 + js, text_promise, 597 + js_mkfun(crash_report_print_url), 598 + js_mkfun(crash_report_noop) 599 + ); 600 + 601 + promise_mark_handled(text_promise); 602 + promise_mark_handled(print_promise); 603 + 604 + return js_mkundef(); 605 + } 606 + 607 + static void crash_report_url(char *out, size_t out_cap) { 608 + const char *base = getenv("ANT_CRASH_REPORT_URL"); 609 + if (!base || !*base) base = "https://js.report"; 610 + 611 + size_t n = strlen(base); 612 + while (n > 0 && base[n - 1] == '/') n--; 613 + if (n >= out_cap) n = out_cap - 1; 614 + 615 + memcpy(out, base, n); 616 + out[n] = '\0'; 617 + strncat(out, "/report", out_cap - strlen(out) - 1); 618 + } 619 + 620 + static char *crash_read_stdin(size_t *len) { 621 + size_t cap = 4096; 622 + *len = 0; 623 + char *buf = malloc(cap); 624 + if (!buf) return NULL; 625 + 626 + size_t n = 0; 627 + while ((n = fread(buf + *len, 1, cap - *len, stdin)) > 0) { 628 + *len += n; 629 + if (*len == cap) { 630 + cap *= 2; 631 + char *next = realloc(buf, cap); 632 + if (!next) { 633 + free(buf); 634 + return NULL; 635 + } 636 + buf = next; 637 + }} 638 + 639 + buf[*len] = '\0'; 640 + return buf; 641 + } 642 + 643 + static ant_value_t crash_get(ant_t *js, ant_value_t obj, const char *key) { 644 + if (!is_object_type(obj)) return js_mkundef(); 645 + return js_get(js, obj, key); 646 + } 647 + 648 + static const char *crash_get_string(ant_t *js, ant_value_t obj, const char *key, size_t *len, const char *fallback) { 649 + ant_value_t value = crash_get(js, obj, key); 650 + if (vtype(value) == T_STR) { 651 + const char *s = js_getstr(js, value, len); 652 + if (s) return s; 653 + } 654 + *len = strlen(fallback); 655 + return fallback; 656 + } 657 + 658 + static unsigned long long crash_get_uint(ant_t *js, ant_value_t obj, const char *key) { 659 + ant_value_t value = crash_get(js, obj, key); 660 + if (vtype(value) != T_NUM) return 0; 661 + double n = js_getnum(value); 662 + if (n <= 0) return 0; 663 + return (unsigned long long)n; 664 + } 665 + 666 + static void crash_format_bytes(char *out, size_t out_cap, unsigned long long bytes) { 667 + if (bytes == 0) { 668 + snprintf(out, out_cap, "unknown"); 669 + return; 670 + } 671 + 672 + if (bytes >= 1024ULL * 1024ULL) 673 + snprintf(out, out_cap, "%lluMB", bytes / (1024ULL * 1024ULL)); 674 + else if (bytes >= 1024ULL) 675 + snprintf(out, out_cap, "%lluKB", bytes / 1024ULL); 676 + else 677 + snprintf(out, out_cap, "%lluB", bytes); 678 + } 679 + 680 + static void crash_print_quoted_value(ant_t *js, ant_value_t value) { 681 + size_t len = 0; 682 + const char *s = NULL; 683 + 684 + if (vtype(value) == T_STR) s = js_getstr(js, value, &len); 685 + if (!s) { 686 + s = "<unknown>"; 687 + len = strlen(s); 688 + } 689 + 690 + fputc('"', stderr); 691 + for (size_t i = 0; i < len; i++) { 692 + char c = s[i]; 693 + if (c == '"' || c == '\\') fputc('\\', stderr); 694 + if (c == '\n' || c == '\r') c = ' '; 695 + fputc(c, stderr); 696 + } 697 + 698 + fputc('"', stderr); 699 + } 700 + 701 + static void crash_print_string_value(ant_t *js, ant_value_t value) { 702 + size_t len = 0; 703 + const char *s = NULL; 704 + 705 + if (vtype(value) == T_STR) s = js_getstr(js, value, &len); 706 + if (!s) { 707 + s = "<unknown>"; 708 + len = strlen(s); 709 + } 710 + 711 + for (size_t i = 0; i < len; i++) { 712 + char c = s[i]; 713 + if (c == '\n' || c == '\r') c = ' '; 714 + fputc(c, stderr); 715 + } 716 + } 717 + 718 + static void crash_print_args(ant_t *js, ant_value_t argv) { 719 + crfprintf(stderr, "<dim>Args:"); 720 + if (vtype(argv) != T_ARR || js_arr_len(js, argv) == 0) { 721 + fputs(" \"ant\"\n", stderr); 722 + return; 723 + } 724 + 725 + ant_offset_t len = js_arr_len(js, argv); 726 + for (ant_offset_t i = 0; i < len; i++) { 727 + fputc(' ', stderr); 728 + crash_print_quoted_value(js, js_arr_get(js, argv, i)); 729 + } 730 + fputc('\n', stderr); 731 + } 732 + 733 + static void crash_print_frames(ant_t *js, ant_value_t report) { 734 + ant_value_t frames = crash_get(js, report, "frames"); 735 + crfprintf(stderr, "<dim>Native backtrace:</>\n"); 736 + if (vtype(frames) != T_ARR || js_arr_len(js, frames) == 0) { 737 + crfprintf(stderr, " <dim>(no native frames were captured)</>\n\n"); 738 + return; 739 + } 740 + 741 + ant_offset_t len = js_arr_len(js, frames); 742 + for (ant_offset_t i = 0; i < len; i++) { 743 + fputs(" ", stderr); 744 + fprintf(stderr, "%-2lld ", (long long)i); 745 + crash_print_string_value(js, js_arr_get(js, frames, i)); 746 + fputc('\n', stderr); 747 + } 748 + fputc('\n', stderr); 749 + } 750 + 751 + static void crash_print_report_summary(ant_t *js, ant_value_t report, ant_value_t argv, bool upload, bool trace, unsigned long long pid) { 752 + crprintf_var("version", ANT_VERSION); 753 + crprintf_var("target", ANT_TARGET_TRIPLE); 754 + crprintf_var("git_hash", ANT_GIT_HASH); 755 + 756 + crprintf_var("os_name", os_name()); 757 + crprintf_var("os_version", "os_version"); 758 + 759 + size_t code_len = 0, addr_len = 0, reason_len = 0; 760 + 761 + const char *code = crash_get_string(js, report, "code", &code_len, "SIGNAL"); 762 + const char *addr = crash_get_string(js, report, "addr", &addr_len, "0x0"); 763 + const char *reason = crash_get_string(js, report, "reason", &reason_len, "Fatal signal"); 764 + 765 + unsigned long long elapsed = crash_get_uint(js, report, "elapsedMs"); 766 + unsigned long long peak_rss = crash_get_uint(js, report, "peakRss"); 767 + 768 + const char *detail = crash_code_detail(code, code_len); 769 + int signal_number = crash_code_signal_number(code, code_len); 770 + 771 + char peak_rss_text[32]; 772 + crash_format_bytes(peak_rss_text, sizeof(peak_rss_text), peak_rss); 773 + 774 + fprintf(stderr, "=== (%llu) ===================================================\n", pid); 775 + 776 + // TODO: Ant v<version> (<git_hash>) <arch> 777 + crfprintf(stderr, "<dim>Ant {version} {git_hash} {target}\n"); 778 + crfprintf(stderr, "<dim>{os_name} {os_version}\n"); 779 + 780 + crash_print_args(js, argv); 781 + 782 + crfprintf(stderr, "<dim>Summary: %s (%.*s) with signal %d\n", detail, code_len, code, signal_number); 783 + crfprintf(stderr, "<dim>Elapsed: %llums | RSS Peak: %s\n\n", elapsed, peak_rss_text); 784 + crfprintf(stderr, "<red>panic</red><dim>(main thread):</> %.*s at address %.*s \n", reason_len, reason, addr_len, addr); 785 + 786 + crfprintf(stderr, "oh no<dim>:</> Ant has crashed. This indicates a bug in Ant, not your code.\n\n"); 787 + if (trace) crash_print_frames(js, report); 788 + 789 + if (upload) { 790 + crfprintf(stderr, "To send a redacted crash report to Ant's team,\n"); 791 + crfprintf(stderr, "please file a GitHub issue using the link below:\n\n"); 792 + if (crash_report_status_inline) crfprintf(stderr, " <yellow>uploading...</>"); 793 + } 794 + else crfprintf(stderr, "Crash reporting is disabled for this process.\n\n"); 795 + } 796 + 797 + int ant_crash_run_internal_report(ant_t *js) { 798 + if (!js) return EXIT_FAILURE; 799 + 800 + size_t payload_len = 0; 801 + char *payload = crash_read_stdin(&payload_len); 802 + if (!payload) return EXIT_FAILURE; 803 + 804 + crash_report_status_printed = false; 805 + 806 + ant_value_t wrapper_json = js_mkstr(js, payload, payload_len); 807 + ant_value_t wrapper = json_parse_value(js, wrapper_json); 808 + 809 + if (is_err(wrapper) || !is_object_type(wrapper)) { 810 + // TODO: make this dry 811 + crfprintf(stderr, "<red>Crash report upload failed.</red>\n"); 812 + free(payload); 813 + return EXIT_FAILURE; 814 + } 815 + 816 + ant_value_t report = crash_get(js, wrapper, "report"); 817 + if (!is_object_type(report)) { 818 + crfprintf(stderr, "<red>Crash report upload failed.</red>\n"); 819 + free(payload); 820 + return EXIT_FAILURE; 821 + } 822 + 823 + bool upload = js_truthy(js, crash_get(js, wrapper, "upload")); 824 + bool trace = js_truthy(js, crash_get(js, wrapper, "trace")); 825 + 826 + unsigned long long pid = crash_get_uint(js, wrapper, "pid"); 827 + ant_value_t argv = crash_get(js, wrapper, "argv"); 828 + 829 + crash_report_status_inline = upload && crash_stderr_is_tty(); 830 + crash_print_report_summary(js, report, argv, upload, trace, pid); 831 + 832 + if (!upload) { 833 + free(payload); 834 + return EXIT_SUCCESS; 835 + } 836 + 837 + ant_value_t report_json = js_json_stringify(js, &report, 1); 838 + if (vtype(report_json) != T_STR) { 839 + // TODO: make this dry 840 + if (crash_report_status_inline) crfprintf(stderr, "\r\033[2K <red>Crash report upload failed.</red>\n"); 841 + else crfprintf(stderr, "<red>Crash report upload failed.</red>\n"); 842 + free(payload); 843 + return EXIT_FAILURE; 844 + } 845 + 846 + size_t report_payload_len = 0; 847 + const char *report_payload = js_getstr(js, report_json, &report_payload_len); 848 + if (!report_payload) { 849 + if (crash_report_status_inline) crfprintf(stderr, "\r\033[2K <red>Crash report upload failed.</red>\n"); 850 + else crfprintf(stderr, "<red>Crash report upload failed.</red>\n"); 851 + free(payload); 852 + return EXIT_FAILURE; 853 + } 854 + 855 + fflush(stderr); 856 + char url[512] = ""; 857 + crash_report_url(url, sizeof(url)); 858 + 859 + ant_value_t headers = js_mkobj(js); 860 + js_set(js, headers, "content-type", js_mkstr(js, "application/json", 16)); 861 + 862 + ant_value_t init = js_mkobj(js); 863 + js_set(js, init, "headers", headers); 864 + js_set(js, init, "method", js_mkstr(js, "POST", 4)); 865 + js_set(js, init, "body", js_mkstr(js, report_payload, report_payload_len)); 866 + 867 + ant_value_t fetch_args[2] = { js_mkstr(js, url, strlen(url)), init }; 868 + ant_value_t fetch_promise = ant_fetch(js, fetch_args, 2); 869 + 870 + if (is_err(fetch_promise)) { 871 + crash_report_status_printed = true; 872 + if (crash_report_status_inline) crfprintf(stderr, "\r\033[2K <red>Crash report upload failed.</red>\n"); 873 + else crfprintf(stderr, "<red>Crash report upload failed.</red>\n"); 874 + const char *message = js_str(js, fetch_promise); 875 + if (message && *message) fprintf(stderr, "%s\n", message); 876 + free(payload); 877 + return EXIT_FAILURE; 878 + } 879 + 880 + ant_value_t report_promise = js_promise_then( 881 + js, fetch_promise, 882 + js_mkfun(crash_report_response_text), 883 + js_mkfun(crash_report_noop) 884 + ); 885 + 886 + promise_mark_handled(fetch_promise); 887 + if (is_err(report_promise)) { 888 + crash_report_status_printed = true; 889 + // TODO: make this dry 890 + if (crash_report_status_inline) crfprintf(stderr, "\r\033[2K <red>Crash report upload failed.</red>\n"); 891 + else crfprintf(stderr, "<red>Crash report upload failed.</red>\n"); 892 + const char *message = js_str(js, report_promise); 893 + if (message && *message) fprintf(stderr, "%s\n", message); 894 + free(payload); 895 + return EXIT_FAILURE; 896 + } 897 + 898 + promise_mark_handled(report_promise); 899 + js_run_event_loop(js); 900 + free(payload); 901 + 902 + return EXIT_SUCCESS; 903 + } 904 + 905 + void ant_crash_suppress_reporting(void) { 906 + crash_reporting_suppressed = 1; 37 907 } 38 908 909 + #ifndef _WIN32 39 910 static int install_altstack(void) { 40 911 #ifdef SA_ONSTACK 41 912 static void *stack_mem; ··· 58 929 return 0; 59 930 } 60 931 61 - static const char *signal_name(int sig) { 62 - switch (sig) { 63 - case SIGSEGV: return "SIGSEGV (invalid memory access)"; 64 - case SIGBUS: return "SIGBUS (bus error)"; 65 - case SIGFPE: return "SIGFPE (arithmetic exception)"; 66 - case SIGILL: return "SIGILL (illegal instruction)"; 67 - case SIGABRT: return "SIGABRT (abort)"; 68 - default: return "fatal signal"; 69 - }} 70 - 71 932 static void crash_handler(int sig, siginfo_t *info, void *ucontext) { 72 933 struct sigaction dfl; 73 934 memset(&dfl, 0, sizeof(dfl)); ··· 76 937 sigemptyset(&dfl.sa_mask); 77 938 sigaction(sig, &dfl, NULL); 78 939 79 - int fd = STDERR_FILENO; 80 - as_write(fd, "\n=== ant crashed: "); 81 - as_write(fd, signal_name(sig)); 82 - as_write(fd, " (signal "); 83 - as_write_uint(fd, (unsigned long)sig); 84 - as_write(fd, ") ===\n"); 85 - 86 - if (info) { 87 - as_write(fd, " fault address: 0x"); 88 - char hex[2 + sizeof(void *) * 2 + 1]; 89 - int hi = (int)sizeof(hex); 90 - 91 - hex[--hi] = '\0'; 92 - uintptr_t addr = (uintptr_t)info->si_addr; 93 - 94 - if (addr == 0) hex[--hi] = '0'; 95 - else while (addr && hi > 0) { 96 - unsigned d = (unsigned)(addr & 0xF); 97 - hex[--hi] = (char)(d < 10 ? '0' + d : 'a' + d - 10); 98 - addr >>= 4; 99 - } 100 - 101 - as_write(fd, &hex[hi]); 102 - as_write(fd, "\n"); 103 - } 104 - 105 - as_write(fd, " ant version: " ANT_VERSION "\n"); 106 - as_write(fd, " pid: "); 107 - as_write_uint(fd, (unsigned long)getpid()); 108 - as_write(fd, "\n\nNative backtrace:\n"); 109 - 940 + uintptr_t frames[ANT_CRASH_FRAME_MAX] = {0}; 941 + int frame_count = 0; 110 942 #ifdef ANT_CRASH_HAVE_EXECINFO 111 - void *frames[64]; 112 - int n = backtrace(frames, (int)(sizeof(frames) / sizeof(frames[0]))); 113 - int skip = n > 2 ? 2 : 0; 114 - if (skip) as_write(fd, " (omitted crash_handler and signal trampoline frames)\n"); 115 - backtrace_symbols_fd(frames + skip, n - skip, fd); 116 - #else 117 - as_write(fd, " (no execinfo support on this platform)\n"); 943 + void *raw_frames[64]; 944 + int n = backtrace(raw_frames, (int)(sizeof(raw_frames) / sizeof(raw_frames[0]))); 945 + int skip = n > 1 ? 1 : 0; 946 + for (int i = skip; i < n && frame_count < ANT_CRASH_FRAME_MAX; i++) 947 + frames[frame_count++] = (uintptr_t)raw_frames[i]; 118 948 #endif 119 949 120 - as_write(fd, 121 - "\nPlease report this at https://github.com/themackabu/ant/issues\n" 122 - "Include the backtrace above and a minimal reproducer if possible.\n\n"); 950 + uint64_t fault_addr = info ? (uint64_t)(uintptr_t)info->si_addr : 0; 951 + char payload[ANT_CRASH_PAYLOAD_MAX] = ""; 952 + const char *reason = posix_signal_reason(sig); 953 + const char *code = posix_signal_code(sig); 954 + 955 + size_t payload_len = build_report_payload( 956 + payload, sizeof(payload), "signal", code, 957 + reason, fault_addr, frames, frame_count 958 + ); 123 959 960 + spawn_reporter(payload, payload_len); 961 + #ifdef SIGTRAP 962 + struct sigaction trap_dfl; 963 + memset(&trap_dfl, 0, sizeof(trap_dfl)); 964 + trap_dfl.sa_handler = SIG_DFL; 965 + sigemptyset(&trap_dfl.sa_mask); 966 + sigaction(SIGTRAP, &trap_dfl, NULL); 967 + raise(SIGTRAP); 968 + #else 124 969 raise(sig); 970 + #endif 125 971 } 126 972 127 - void ant_crash_init(void) { 973 + void ant_crash_init(int argc, char **argv) { 974 + crash_start_ms = now_ms(); 975 + init_exe_path(argc, argv); 976 + init_argv_strings(argc, argv); 977 + init_report_controls(); 978 + 128 979 struct sigaction sa; 129 980 memset(&sa, 0, sizeof(sa)); 130 981 sa.sa_sigaction = crash_handler; ··· 133 984 if (install_altstack()) sa.sa_flags |= SA_ONSTACK; 134 985 #endif 135 986 sigemptyset(&sa.sa_mask); 136 - 137 987 static const int sigs[] = { SIGSEGV, SIGBUS, SIGFPE, SIGILL, SIGABRT }; 138 - for (size_t i = 0; i < sizeof(sigs) / sizeof(sigs[0]); i++) { 139 - sigaction(sigs[i], &sa, NULL); 140 - } 988 + for (size_t i = 0; i < sizeof(sigs) / sizeof(sigs[0]); i++) sigaction(sigs[i], &sa, NULL); 141 989 } 142 990 143 991 #else // _WIN32 144 - 145 - #include <dbghelp.h> 146 992 #include <process.h> 147 993 148 994 static LPTOP_LEVEL_EXCEPTION_FILTER previous_filter; ··· 172 1018 default: return "fatal exception"; 173 1019 }} 174 1020 1021 + static const char *exception_reason(DWORD code) { 1022 + switch (code) { 1023 + case EXCEPTION_ACCESS_VIOLATION: return "Segmentation fault"; 1024 + case EXCEPTION_ILLEGAL_INSTRUCTION: return "Illegal instruction"; 1025 + case EXCEPTION_STACK_OVERFLOW: return "Stack overflow"; 1026 + case EXCEPTION_INT_DIVIDE_BY_ZERO: 1027 + case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "Divide by zero"; 1028 + default: return "Fatal exception"; 1029 + }} 1030 + 175 1031 static DWORD64 exception_fault_address(EXCEPTION_RECORD *record) { 176 1032 if (!record) return 0; 177 1033 if (( 178 - record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || 179 - record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) 1034 + record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION || 1035 + record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR) 180 1036 && record->NumberParameters >= 2 181 1037 ) return (DWORD64)record->ExceptionInformation[1]; 182 1038 return (DWORD64)(uintptr_t)record->ExceptionAddress; ··· 184 1040 185 1041 static BOOL init_stack_frame(CONTEXT *ctx, STACKFRAME64 *frame, DWORD *machine) { 186 1042 memset(frame, 0, sizeof(*frame)); 187 - 188 1043 #if defined(_M_X64) || defined(__x86_64__) 189 1044 *machine = IMAGE_FILE_MACHINE_AMD64; 190 1045 frame->AddrPC.Offset = ctx->Rip; ··· 209 1064 return TRUE; 210 1065 } 211 1066 1067 + static int collect_windows_frames(EXCEPTION_POINTERS *exc, uintptr_t *frames, int max_frames) { 1068 + if (!exc || !exc->ContextRecord) return 0; 1069 + HANDLE process = GetCurrentProcess(); 1070 + HANDLE thread = GetCurrentThread(); 1071 + CONTEXT ctx = *exc->ContextRecord; 1072 + STACKFRAME64 frame; 1073 + DWORD machine; 1074 + if (!init_stack_frame(&ctx, &frame, &machine)) return 0; 1075 + 1076 + int count = 0; 1077 + while (count < max_frames) { 1078 + DWORD64 addr = frame.AddrPC.Offset; 1079 + if (addr == 0) break; 1080 + frames[count++] = (uintptr_t)addr; 1081 + DWORD64 prev_pc = frame.AddrPC.Offset; 1082 + DWORD64 prev_sp = frame.AddrStack.Offset; 1083 + BOOL ok = StackWalk64( 1084 + machine, process, thread, &frame, &ctx, NULL, 1085 + SymFunctionTableAccess64, SymGetModuleBase64, NULL 1086 + ); 1087 + if (!ok || (frame.AddrPC.Offset == prev_pc && frame.AddrStack.Offset == prev_sp)) break; 1088 + } 1089 + return count; 1090 + } 1091 + 212 1092 static void print_windows_backtrace(EXCEPTION_POINTERS *exc) { 213 1093 if (!exc || !exc->ContextRecord) { 214 1094 fprintf(stderr, " (no exception context available)\n"); ··· 216 1096 } 217 1097 218 1098 HANDLE process = GetCurrentProcess(); 219 - HANDLE thread = GetCurrentThread(); 220 - 221 1099 SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); 222 1100 if (!SymInitialize(process, NULL, TRUE)) { 223 1101 fprintf(stderr, " (SymInitialize failed: %lu)\n", GetLastError()); 224 1102 return; 225 1103 } 226 1104 227 - CONTEXT ctx = *exc->ContextRecord; 228 - STACKFRAME64 frame; 229 - DWORD machine; 230 - if (!init_stack_frame(&ctx, &frame, &machine)) { 231 - fprintf(stderr, " (unsupported Windows architecture for StackWalk64)\n"); 232 - return; 233 - } 234 - 1105 + uintptr_t frames[ANT_CRASH_FRAME_MAX] = {0}; 1106 + int frame_count = collect_windows_frames(exc, frames, ANT_CRASH_FRAME_MAX); 1107 + 235 1108 union { 236 1109 SYMBOL_INFO info; 237 1110 char storage[sizeof(SYMBOL_INFO) + MAX_SYM_NAME]; ··· 242 1115 symbol->SizeOfStruct = sizeof(SYMBOL_INFO); 243 1116 symbol->MaxNameLen = MAX_SYM_NAME; 244 1117 245 - for (int i = 0; i < 64; i++) { 246 - DWORD64 addr = frame.AddrPC.Offset; 247 - if (addr == 0) break; 1118 + for (int i = 0; i < frame_count; i++) { 1119 + DWORD64 addr = (DWORD64)frames[i]; 248 1120 DWORD64 displacement = 0; 249 1121 250 1122 fprintf(stderr, "%d 0x%016llx", i, (unsigned long long)addr); 251 - if (SymFromAddr(process, addr, &displacement, symbol)) 1123 + if (SymFromAddr(process, addr, &displacement, symbol)) 252 1124 fprintf(stderr, " %s + %llu", symbol->Name, (unsigned long long)displacement); 253 - 1125 + 254 1126 IMAGEHLP_LINE64 line; 255 1127 DWORD line_disp = 0; 256 1128 memset(&line, 0, sizeof(line)); 257 1129 line.SizeOfStruct = sizeof(line); 258 - if (SymGetLineFromAddr64(process, addr, &line_disp, &line)) 1130 + if (SymGetLineFromAddr64(process, addr, &line_disp, &line)) 259 1131 fprintf(stderr, " (%s:%lu)", line.FileName, line.LineNumber); 260 - 261 1132 fputc('\n', stderr); 262 - DWORD64 prev_pc = frame.AddrPC.Offset; 263 - DWORD64 prev_sp = frame.AddrStack.Offset; 264 - BOOL ok = StackWalk64( 265 - machine, process, thread, &frame, &ctx, NULL, 266 - SymFunctionTableAccess64, SymGetModuleBase64, NULL 267 - ); 268 - if (!ok || (frame.AddrPC.Offset == prev_pc && frame.AddrStack.Offset == prev_sp)) break; 269 1133 } 270 1134 } 271 1135 272 1136 static LONG WINAPI windows_crash_handler(EXCEPTION_POINTERS *exc) { 273 - if (InterlockedExchange(&crash_in_progress, 1) != 0) { 1137 + if (InterlockedExchange(&crash_in_progress, 1) != 0) 274 1138 return EXCEPTION_CONTINUE_SEARCH; 275 - } 1139 + 1140 + HANDLE process = GetCurrentProcess(); 1141 + SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); 1142 + SymInitialize(process, NULL, TRUE); 276 1143 277 1144 EXCEPTION_RECORD *record = exc ? exc->ExceptionRecord : NULL; 278 1145 DWORD code = record ? record->ExceptionCode : 0; 1146 + uint64_t fault_addr = (uint64_t)exception_fault_address(record); 279 1147 280 - fprintf(stderr, "\n=== ant crashed: %s (0x%08lx) ===\n", exception_name(code), (unsigned long)code); 281 - fprintf(stderr, " fault address: 0x%016llx\n", (unsigned long long)exception_fault_address(record)); 282 - fprintf(stderr, " ant version: " ANT_VERSION "\n"); 283 - fprintf(stderr, " pid: %lu\n\n", (unsigned long)_getpid()); 284 - fprintf(stderr, "Native backtrace:\n"); 285 - print_windows_backtrace(exc); 1148 + uintptr_t frames[ANT_CRASH_FRAME_MAX] = {0}; 1149 + int frame_count = collect_windows_frames(exc, frames, ANT_CRASH_FRAME_MAX); 1150 + char payload[ANT_CRASH_PAYLOAD_MAX] = ""; 286 1151 287 - fprintf(stderr, 288 - "\nPlease report this at https://github.com/themackabu/ant/issues\n" 289 - "Include the backtrace above and a minimal reproducer if possible.\n\n"); 1152 + size_t payload_len = build_report_payload( 1153 + payload, sizeof(payload), "exception", exception_name(code), 1154 + exception_reason(code), fault_addr, frames, frame_count 1155 + ); 290 1156 1157 + spawn_reporter(payload, payload_len); 291 1158 if (previous_filter) return previous_filter(exc); 292 1159 return EXCEPTION_EXECUTE_HANDLER; 293 1160 } 294 1161 295 - void ant_crash_init(void) { 1162 + void ant_crash_init(int argc, char **argv) { 1163 + crash_start_ms = now_ms(); 1164 + init_exe_path(argc, argv); 1165 + init_argv_strings(argc, argv); 1166 + init_report_controls(); 296 1167 previous_filter = SetUnhandledExceptionFilter(windows_crash_handler); 297 1168 } 298 1169
+9 -2
src/main.c
··· 379 379 } 380 380 381 381 int main(int argc, char *argv[]) { 382 - if (!getenv("ANT_NO_CRASH_HANDLER")) ant_crash_init(); 382 + bool internal_crash_report_mode = ant_crash_is_internal_report(argc, argv); 383 + 384 + if (internal_crash_report_mode) argc = 1; 385 + if (!internal_crash_report_mode && !getenv("ANT_NO_CRASH_HANDLER")) ant_crash_init(argc, argv); 383 386 384 387 setup_console_colors(); 385 388 parse_ant_debug_flags(); ··· 688 691 crfprintf(stderr, msg.snapshot_warn, js_str(js, snapshot_result)); 689 692 } 690 693 691 - if (eval->count > 0) { 694 + if (internal_crash_report_mode) { 695 + js_result = ant_crash_run_internal_report(js); 696 + } 697 + 698 + else if (eval->count > 0) { 692 699 const char *script = eval->sval[0]; 693 700 eval_code(js, script, strlen(script), "[eval]", print->count > 0); 694 701 }
+8
src/modules/builtin.c
··· 16 16 17 17 #include "ant.h" 18 18 #include "gc.h" 19 + #include "crash.h" 19 20 #include "errors.h" 20 21 #include "runtime.h" 21 22 #include "internal.h" ··· 160 161 if (nargs < 1) return js_mkerr(js, "Ant.usleep() requires 1 argument"); 161 162 useconds_t us = (useconds_t)js_getnum(args[0]); 162 163 usleep(us); 164 + return js_mkundef(); 165 + } 166 + 167 + // Ant.suppressReporting() 168 + static ant_value_t js_suppress_reporting(ant_t *js, ant_value_t *args, int nargs) { 169 + ant_crash_suppress_reporting(); 163 170 return js_mkundef(); 164 171 } 165 172 ··· 457 464 js_set(js, ant_obj, "sleep", js_mkfun(js_sleep)); 458 465 js_set(js, ant_obj, "msleep", js_mkfun(js_msleep)); 459 466 js_set(js, ant_obj, "usleep", js_mkfun(js_usleep)); 467 + js_set(js, ant_obj, "suppressReporting", js_mkfun(js_suppress_reporting)); 460 468 461 469 ant_value_t hl_obj = js_newobj(js); 462 470 ant_value_t hl_fn = js_obj_to_func(hl_obj);
+4 -2
src/modules/fetch.c
··· 847 847 return js_mkundef(); 848 848 } 849 849 850 - static ant_value_t js_fetch(ant_t *js, ant_value_t *args, int nargs) { 850 + ant_value_t ant_fetch(ant_t *js, ant_value_t *args, int nargs) { 851 851 ant_value_t input = (nargs >= 1) ? args[0] : js_mkundef(); 852 852 ant_value_t init = (nargs >= 2) ? args[1] : js_mkundef(); 853 + 853 854 ant_value_t promise = js_mkpromise(js); 854 855 ant_value_t request_obj = 0; 856 + 855 857 request_data_t *request = NULL; 856 858 fetch_request_t *req = NULL; 857 859 ant_value_t signal = 0; ··· 903 905 904 906 void init_fetch_module() { 905 907 utarray_new(pending_requests, &ut_ptr_icd); 906 - js_set(rt->js, rt->js->global, "fetch", js_mkfun_flags(js_fetch, CFUNC_HAS_PROTOTYPE)); 908 + js_set(rt->js, rt->js->global, "fetch", js_mkfun_flags(ant_fetch, CFUNC_HAS_PROTOTYPE)); 907 909 } 908 910 909 911 int has_pending_fetches(void) {
+1
src/types/ant.d.ts
··· 163 163 164 164 raw: AntRaw; 165 165 stats(): AntStatsResult; 166 + suppressReporting(): void; 166 167 167 168 sleep(seconds: number): void; 168 169 msleep(milliseconds: number): void;