jm

Odin for scripts: small packages and a runner, on core: only
Log | Files | Refs | README

fuzz.odin (23180B)


      1 /*
      2 Package fuzz throws randomly generated values and randomly damaged SQL at
      3 jm:sqlite3 and checks the properties that must hold whatever comes back.
      4 
      5 	report := fuzz.run({seed = 1, iterations = 10_000})
      6 	for f in report.failures {
      7 		fmt.eprintf("%s failed at iteration %d: %s\n", f.property, f.iteration, f.detail)
      8 	}
      9 
     10 Every run is a pure function of its seed, so a failure replays exactly:
     11 `just fuzz seed=<seed>` runs the same cases in the same order. A report names
     12 the seed it used even when it was asked for a random one.
     13 
     14 The properties are the promises the package makes that a test with fixed
     15 inputs can only sample:
     16 
     17 	round_trip      a bound value reads back as itself, whatever bytes it holds
     18 	injection       a value is never parsed as SQL, however much it looks like it
     19 	damaged_sql     broken SQL faults and leaves the connection usable
     20 	arity           an argument list that does not match the statement is refused
     21 	atomicity       a transaction that fails leaves nothing behind
     22 	reuse           bind, step and reset in any order keep a statement honest
     23 
     24 What this cannot see: SQLite reuses memory from its own pool, so reading a
     25 column after the next step returns stale bytes rather than tripping
     26 AddressSanitizer. The clone in text and blob is what prevents it, and only
     27 round_trip's comparison catches a regression there.
     28 */
     29 package fuzz
     30 
     31 import "base:runtime"
     32 import "core:fmt"
     33 import "core:math"
     34 import "core:math/rand"
     35 import "core:mem"
     36 import "core:slice"
     37 import "core:strings"
     38 import "core:sync"
     39 import "core:thread"
     40 import "core:time"
     41 
     42 import "jm:sqlite3"
     43 
     44 // Opts bounds a run. The zero value is one thousand iterations from a seed
     45 // taken off the clock.
     46 Opts :: struct {
     47 	// The seed to replay. 0 takes one from the clock and reports it.
     48 	seed:          u64,
     49 	// How many cases to run. 0 means one thousand, or until duration runs out.
     50 	iterations:    int,
     51 	// Stop after this long, however many iterations are left. 0 is no limit.
     52 	duration:      time.Duration,
     53 	// Stop at the first failure rather than collecting them all.
     54 	stop_on_first: bool,
     55 	// How long one case may run before it is interrupted and reported as a
     56 	// hang. 0 means five seconds. Nothing in SQLite bounds a statement, and
     57 	// a recursive query can return rows without end, so a harness with no
     58 	// watchdog stops being a harness the first time it generates one.
     59 	case_timeout:  time.Duration,
     60 	// Called once per property failure, and once per 1000 iterations when
     61 	// progress is worth showing. nil is silent.
     62 	log:           proc(format: string, args: ..any),
     63 }
     64 
     65 // Failure is one property that did not hold, with enough to reproduce it.
     66 Failure :: struct {
     67 	property:  string,
     68 	iteration: int,
     69 	// The seed the whole run used; replaying it reaches this case again.
     70 	seed:      u64,
     71 	// What differed, in full: the value bound and the value read back.
     72 	detail:    string,
     73 }
     74 
     75 // Report is what a run found. Its failures are allocated in the allocator
     76 // run was given, and belong to the caller.
     77 Report :: struct {
     78 	seed:       u64,
     79 	iterations: int,
     80 	elapsed:    time.Duration,
     81 	failures:   []Failure,
     82 	// A fingerprint of the randomness each case consumed. Two runs of the
     83 	// same seed agree on it; a run that generated anything differently does
     84 	// not. It is what makes determinism checkable when nothing failed.
     85 	digest:     u64,
     86 }
     87 
     88 // watchdog interrupts a case that overruns. The mutex covers both fields, so
     89 // the connection cannot be closed between the deadline check and the
     90 // interrupt that follows it.
     91 @(private)
     92 Watchdog :: struct {
     93 	mutex:    sync.Mutex,
     94 	db:       sqlite3.Db,
     95 	deadline: time.Time,
     96 	fired:    bool,
     97 	stop:     bool,
     98 }
     99 
    100 // Property is one promise, checked against a scratch database that is thrown
    101 // away afterwards. It returns the detail of what went wrong, and whether it
    102 // held.
    103 Property :: struct {
    104 	name:  string,
    105 	check: proc(db: sqlite3.Db) -> (detail: string, ok: bool),
    106 }
    107 
    108 // properties is every promise a run cycles through, one per iteration.
    109 properties := []Property {
    110 	{"round_trip", round_trip},
    111 	{"injection", injection},
    112 	{"damaged_sql", damaged_sql},
    113 	{"arity", arity},
    114 	{"atomicity", atomicity},
    115 	{"reuse", reuse},
    116 }
    117 
    118 // run cycles the properties over generated cases and reports what failed.
    119 run :: proc(opts := Opts{}, allocator := context.allocator) -> Report {
    120 	opts := opts
    121 	if opts.seed == 0 {
    122 		opts.seed = u64(time.now()._nsec) | 1
    123 	}
    124 	if opts.iterations == 0 {
    125 		opts.iterations = 1000
    126 	}
    127 
    128 	state := rand.create(opts.seed)
    129 	context.random_generator = runtime.default_random_generator(&state)
    130 
    131 	dog := new(Watchdog, context.temp_allocator)
    132 	timeout := opts.case_timeout if opts.case_timeout > 0 else 5 * time.Second
    133 	guard := thread.create_and_start_with_poly_data2(dog, timeout, watch)
    134 	defer {
    135 		sync.lock(&dog.mutex)
    136 		dog.stop = true
    137 		sync.unlock(&dog.mutex)
    138 		thread.join(guard)
    139 		thread.destroy(guard)
    140 	}
    141 
    142 	failures := make([dynamic]Failure, allocator)
    143 	started := time.now()
    144 	done := 0
    145 	digest := u64(1469598103934665603)
    146 	for i in 0 ..< opts.iterations {
    147 		if opts.duration > 0 && time.since(started) >= opts.duration {
    148 			break
    149 		}
    150 		done = i + 1
    151 		p := properties[i % len(properties)]
    152 
    153 		// Each case gets its own database and its own arena, so a case can
    154 		// neither inherit state from the last nor keep memory after it.
    155 		arena: mem.Dynamic_Arena
    156 		mem.dynamic_arena_init(&arena)
    157 		defer mem.dynamic_arena_destroy(&arena)
    158 		case_context := context
    159 		case_context.allocator = mem.dynamic_arena_allocator(&arena)
    160 
    161 		detail, ok := run_case(p, case_context, dog, timeout)
    162 		// Drawn after the case, so it reflects how much randomness the case
    163 		// used, not just which property ran.
    164 		digest = (digest ~ rand.uint64()) * 1099511628211
    165 		if !ok {
    166 			// The detail was built in the case arena, which is about to go.
    167 			f := Failure {
    168 				property  = p.name,
    169 				iteration = i,
    170 				seed      = opts.seed,
    171 				detail    = strings.clone(detail, allocator),
    172 			}
    173 			append(&failures, f)
    174 			if opts.log != nil {
    175 				opts.log("%s failed at iteration %d: %s", p.name, i, f.detail)
    176 			}
    177 			if opts.stop_on_first {
    178 				break
    179 			}
    180 		}
    181 		if opts.log != nil && done % 1000 == 0 {
    182 			opts.log("%d iterations, %d failures", done, len(failures))
    183 		}
    184 	}
    185 	return Report {
    186 		seed = opts.seed,
    187 		iterations = done,
    188 		elapsed = time.since(started),
    189 		failures = failures[:],
    190 		digest = digest,
    191 	}
    192 }
    193 
    194 // run_case opens the scratch database, checks one property against it and
    195 // closes it again, under the case's own allocator.
    196 @(private)
    197 run_case :: proc(
    198 	p: Property,
    199 	case_context: runtime.Context,
    200 	dog: ^Watchdog,
    201 	timeout: time.Duration,
    202 ) -> (
    203 	detail: string,
    204 	ok: bool,
    205 ) {
    206 	context = case_context
    207 	db, err := sqlite3.open(sqlite3.MEMORY)
    208 	if err != nil {
    209 		return fmt.tprintf("open failed: %v", err), false
    210 	}
    211 
    212 	sync.lock(&dog.mutex)
    213 	dog.db = db
    214 	dog.deadline = time.time_add(time.now(), timeout)
    215 	dog.fired = false
    216 	sync.unlock(&dog.mutex)
    217 
    218 	detail, ok = p.check(db)
    219 
    220 	// Retire the connection from the watchdog before closing it, so an
    221 	// interrupt can never land on a closed handle.
    222 	sync.lock(&dog.mutex)
    223 	hung := dog.fired
    224 	dog.db = {}
    225 	sync.unlock(&dog.mutex)
    226 	sqlite3.close(&db)
    227 
    228 	if hung {
    229 		// Whatever the property made of the interrupt, the case did not
    230 		// finish on its own, and that is the thing worth reporting.
    231 		if detail == "" {
    232 			return fmt.tprintf("did not finish within %v", timeout), false
    233 		}
    234 		return fmt.tprintf("did not finish within %v, then: %s", timeout, detail), false
    235 	}
    236 	return detail, ok
    237 }
    238 
    239 // watch interrupts a case that has run past its deadline, and keeps
    240 // interrupting until the case retires its connection. Interrupting once is
    241 // not enough: a property runs several statements, and the next one would
    242 // simply hang in place of the one that was stopped.
    243 @(private)
    244 watch :: proc(dog: ^Watchdog, timeout: time.Duration) {
    245 	for {
    246 		time.sleep(10 * time.Millisecond)
    247 		sync.lock(&dog.mutex)
    248 		if dog.stop {
    249 			sync.unlock(&dog.mutex)
    250 			return
    251 		}
    252 		if dog.db.handle != nil && time.since(dog.deadline) > 0 {
    253 			sqlite3.interrupt(dog.db)
    254 			dog.fired = true
    255 		}
    256 		sync.unlock(&dog.mutex)
    257 	}
    258 }
    259 
    260 // round_trip binds one generated value and reads it back. Whatever bytes go
    261 // in come out, and the storage class is the one the value asked for.
    262 round_trip :: proc(db: sqlite3.Db) -> (detail: string, ok: bool) {
    263 	// An untyped column keeps whatever class it is given, with no affinity
    264 	// to convert it on the way in.
    265 	if err := sqlite3.exec(db, `CREATE TABLE t(v)`); err != nil {
    266 		return fmt.tprintf("create: %v", err), false
    267 	}
    268 	v := value()
    269 	if err := sqlite3.exec_args(db, `INSERT INTO t VALUES (?)`, v); err != nil {
    270 		return fmt.tprintf("insert %s: %v", show(v), err), false
    271 	}
    272 	rows, qerr := sqlite3.query(db, `SELECT v FROM t`)
    273 	if qerr != nil {
    274 		return fmt.tprintf("select: %v", qerr), false
    275 	}
    276 	defer sqlite3.finish(&rows)
    277 	if !sqlite3.next(&rows) {
    278 		return fmt.tprintf("%s vanished", show(v)), false
    279 	}
    280 
    281 	want := expected_type(v)
    282 	if got := sqlite3.type_of(rows, 0); got != want {
    283 		return fmt.tprintf("%s stored as %v, wanted %v", show(v), got, want), false
    284 	}
    285 	switch bound in v {
    286 	case i64:
    287 		if got := sqlite3.integer(rows, 0); got != bound {
    288 			return fmt.tprintf("%d read back as %d", bound, got), false
    289 		}
    290 	case f64:
    291 		// SQLite has no NaN: binding one stores NULL, which expected_type
    292 		// already accounts for.
    293 		if !math.is_nan(bound) {
    294 			if got := sqlite3.real(rows, 0); got != bound {
    295 				return fmt.tprintf("%v read back as %v", bound, got), false
    296 			}
    297 		}
    298 	case bool:
    299 		if got := sqlite3.boolean(rows, 0); got != bound {
    300 			return fmt.tprintf("%v read back as %v", bound, got), false
    301 		}
    302 	case string:
    303 		if got := sqlite3.text(rows, 0); got != bound {
    304 			return fmt.tprintf("%s read back as %s", show(v), show(got)), false
    305 		}
    306 	case []byte:
    307 		got := sqlite3.blob(rows, 0)
    308 		if len(got) != len(bound) || !slice.equal(got, bound) {
    309 			return fmt.tprintf("%s read back as %s", show(v), show(got)), false
    310 		}
    311 	}
    312 	return "", true
    313 }
    314 
    315 // injection writes generated text that is trying to look like SQL, and checks
    316 // that none of it was parsed as any. The canary table is what a successful
    317 // injection would drop.
    318 injection :: proc(db: sqlite3.Db) -> (detail: string, ok: bool) {
    319 	if err := sqlite3.exec(db, `CREATE TABLE t(v TEXT); CREATE TABLE canary(x)`); err != nil {
    320 		return fmt.tprintf("create: %v", err), false
    321 	}
    322 	count := rand.int_range(1, 16)
    323 	written := make([]string, count, context.temp_allocator)
    324 	for i in 0 ..< count {
    325 		written[i] = sql_shaped_text()
    326 		if err := sqlite3.exec_args(db, `INSERT INTO t VALUES (?)`, written[i]); err != nil {
    327 			return fmt.tprintf("insert %s: %v", show(written[i]), err), false
    328 		}
    329 	}
    330 
    331 	got, gerr := collect(db, `SELECT v FROM t ORDER BY rowid`)
    332 	if gerr != "" {
    333 		return gerr, false
    334 	}
    335 	if len(got) != count {
    336 		return fmt.tprintf("wrote %d rows, read %d", count, len(got)), false
    337 	}
    338 	for want, i in written {
    339 		if got[i] != want {
    340 			return fmt.tprintf("row %d: %s read back as %s", i, show(want), show(got[i])), false
    341 		}
    342 	}
    343 
    344 	// Nothing bound may have reached the parser, so the canary is untouched.
    345 	check, cerr := sqlite3.query(db, `SELECT count(*) FROM canary`)
    346 	if cerr != nil {
    347 		return fmt.tprintf("canary gone: %v", cerr), false
    348 	}
    349 	defer sqlite3.finish(&check)
    350 	if !sqlite3.next(&check) {
    351 		return "canary unreadable", false
    352 	}
    353 	return "", true
    354 }
    355 
    356 // damaged_sql feeds the parser text it should refuse. A refusal is a Fault,
    357 // never a crash, and the connection still works afterwards.
    358 damaged_sql :: proc(db: sqlite3.Db) -> (detail: string, ok: bool) {
    359 	bad := damaged_statement()
    360 	// Either outcome is allowed: damage can land on something valid. What is
    361 	// not allowed is a crash, or a connection that stops answering.
    362 	_ = sqlite3.exec(db, bad)
    363 	if rows, err := sqlite3.query(db, bad); err == nil {
    364 		for sqlite3.next(&rows) {
    365 			// Reading every column of every row is where a wrong column
    366 			// count or a stale pointer would show.
    367 			for col in 0 ..< sqlite3.column_count(rows) {
    368 				_ = sqlite3.type_of(rows, col)
    369 				_ = sqlite3.text(rows, col)
    370 				_ = sqlite3.blob(rows, col)
    371 			}
    372 		}
    373 		_ = sqlite3.finish(&rows)
    374 	}
    375 
    376 	live, lerr := sqlite3.query(db, `SELECT 1`)
    377 	if lerr != nil {
    378 		return fmt.tprintf("connection lost after %s: %v", show(bad), lerr), false
    379 	}
    380 	defer sqlite3.finish(&live)
    381 	if !sqlite3.next(&live) || sqlite3.integer(live, 0) != 1 {
    382 		return fmt.tprintf("connection unusable after %s", show(bad)), false
    383 	}
    384 	return "", true
    385 }
    386 
    387 // arity checks the guard in bind: a list that does not match the statement's
    388 // parameter count is refused, and one that matches is accepted.
    389 arity :: proc(db: sqlite3.Db) -> (detail: string, ok: bool) {
    390 	want := rand.int_range(1, 8)
    391 	marks := make([dynamic]string, context.temp_allocator)
    392 	for _ in 0 ..< want {
    393 		append(&marks, "?")
    394 	}
    395 	sql := fmt.tprintf("SELECT %s", strings.join(marks[:], ", ", context.temp_allocator))
    396 	stmt, perr := sqlite3.prepare(db, sql)
    397 	if perr != nil {
    398 		return fmt.tprintf("prepare %s: %v", sql, perr), false
    399 	}
    400 	defer sqlite3.finish(&stmt)
    401 
    402 	give := rand.int_range(0, 9)
    403 	args := make([]sqlite3.Value, give, context.temp_allocator)
    404 	for i in 0 ..< give {
    405 		args[i] = value()
    406 	}
    407 	err := sqlite3.bind(&stmt, ..args)
    408 	if give == want && err != nil {
    409 		return fmt.tprintf("%d parameters, %d args, refused: %v", want, give, err), false
    410 	}
    411 	if give != want {
    412 		fault, is_fault := err.(sqlite3.Fault)
    413 		if !is_fault {
    414 			return fmt.tprintf("%d parameters, %d args, accepted", want, give), false
    415 		}
    416 		if fault.code != .Range {
    417 			return fmt.tprintf("%d parameters, %d args, gave %v", want, give, fault.code), false
    418 		}
    419 	}
    420 	return "", true
    421 }
    422 
    423 // atomicity rolls a transaction back from a random point and checks that the
    424 // table holds exactly what it held before.
    425 atomicity :: proc(db: sqlite3.Db) -> (detail: string, ok: bool) {
    426 	if err := sqlite3.exec(db, `CREATE TABLE t(v UNIQUE)`); err != nil {
    427 		return fmt.tprintf("create: %v", err), false
    428 	}
    429 	before := rand.int_range(0, 8)
    430 	for i in 0 ..< before {
    431 		if err := sqlite3.exec_args(db, `INSERT INTO t VALUES (?)`, i64(i)); err != nil {
    432 			return fmt.tprintf("seed row %d: %v", i, err), false
    433 		}
    434 	}
    435 
    436 	// The body writes a random number of fresh rows, then collides with one
    437 	// that is already there, which fails the transaction wherever it is.
    438 	doomed := Batch {
    439 		fresh         = rand.int_range(0, 8),
    440 		collide_with  = before > 0 ? i64(rand.int_range(0, before)) : 0,
    441 		has_collision = before > 0,
    442 	}
    443 	err := sqlite3.transact(db, batch_body, &doomed)
    444 	if doomed.has_collision && err == nil {
    445 		return "the colliding transaction was not refused", false
    446 	}
    447 
    448 	rows, qerr := sqlite3.query(db, `SELECT count(*) FROM t`)
    449 	if qerr != nil {
    450 		return fmt.tprintf("count: %v", qerr), false
    451 	}
    452 	defer sqlite3.finish(&rows)
    453 	if !sqlite3.next(&rows) {
    454 		return "count returned no row", false
    455 	}
    456 	got := sqlite3.integer(rows, 0)
    457 	want := i64(before)
    458 	if !doomed.has_collision {
    459 		// With nothing to collide with the body commits, so its rows stay.
    460 		want += i64(doomed.fresh)
    461 	}
    462 	if got != want {
    463 		return fmt.tprintf("%d rows after rollback, wanted %d", got, want), false
    464 	}
    465 	return "", true
    466 }
    467 
    468 // Batch is what batch_body should write inside a transaction. It travels
    469 // through transact's user pointer rather than a package global, so two runs
    470 // in one process cannot rewrite each other's expectations: a package global
    471 // here made the tests fail whenever two of them ran at once.
    472 @(private)
    473 Batch :: struct {
    474 	fresh:         int,
    475 	collide_with:  i64,
    476 	has_collision: bool,
    477 }
    478 
    479 @(private)
    480 batch_body :: proc(db: sqlite3.Db, user: rawptr) -> sqlite3.Error {
    481 	doomed := (^Batch)(user)
    482 	for i in 0 ..< doomed.fresh {
    483 		sqlite3.exec_args(db, `INSERT INTO t VALUES (?)`, i64(1000 + i)) or_return
    484 	}
    485 	if doomed.has_collision {
    486 		sqlite3.exec_args(db, `INSERT INTO t VALUES (?)`, doomed.collide_with) or_return
    487 	}
    488 	return nil
    489 }
    490 
    491 // reuse drives one prepared statement through a random sequence of binds,
    492 // steps and resets, and checks the rows that came out are the rows put in.
    493 reuse :: proc(db: sqlite3.Db) -> (detail: string, ok: bool) {
    494 	if err := sqlite3.exec(db, `CREATE TABLE t(v)`); err != nil {
    495 		return fmt.tprintf("create: %v", err), false
    496 	}
    497 	stmt, perr := sqlite3.prepare(db, `INSERT INTO t VALUES (?)`)
    498 	if perr != nil {
    499 		return fmt.tprintf("prepare: %v", perr), false
    500 	}
    501 
    502 	rounds := rand.int_range(1, 32)
    503 	sent := make([dynamic]string, context.temp_allocator)
    504 	for _ in 0 ..< rounds {
    505 		v := text()
    506 		if err := sqlite3.bind(&stmt, v); err != nil {
    507 			sqlite3.finish(&stmt)
    508 			return fmt.tprintf("bind %s: %v", show(v), err), false
    509 		}
    510 		if sqlite3.next(&stmt) {
    511 			sqlite3.finish(&stmt)
    512 			return "an insert returned a row", false
    513 		}
    514 		if err := sqlite3.reset(&stmt); err != nil {
    515 			sqlite3.finish(&stmt)
    516 			return fmt.tprintf("reset: %v", err), false
    517 		}
    518 		append(&sent, v)
    519 	}
    520 	if err := sqlite3.finish(&stmt); err != nil {
    521 		return fmt.tprintf("finish: %v", err), false
    522 	}
    523 
    524 	got, gerr := collect(db, `SELECT v FROM t ORDER BY rowid`)
    525 	if gerr != "" {
    526 		return gerr, false
    527 	}
    528 	if len(got) != len(sent) {
    529 		return fmt.tprintf("sent %d rows, read %d", len(sent), len(got)), false
    530 	}
    531 	for want, i in sent {
    532 		if got[i] != want {
    533 			return fmt.tprintf("row %d: %s read back as %s", i, show(want), show(got[i])), false
    534 		}
    535 	}
    536 	return "", true
    537 }
    538 
    539 // collect reads a one-column query into a slice and only then compares
    540 // anything, which is the whole point: a column read that handed back SQLite's
    541 // own memory still looks right while the cursor is on the row, and turns into
    542 // the next row's bytes once it has moved. Comparing inside the loop cannot
    543 // see that, so nothing here does.
    544 @(private)
    545 collect :: proc(db: sqlite3.Db, sql: string) -> (out: []string, detail: string) {
    546 	rows, err := sqlite3.query(db, sql)
    547 	if err != nil {
    548 		return nil, fmt.tprintf("select: %v", err)
    549 	}
    550 	read := make([dynamic]string, context.temp_allocator)
    551 	for sqlite3.next(&rows) {
    552 		append(&read, sqlite3.text(rows, 0))
    553 	}
    554 	if ferr := sqlite3.finish(&rows); ferr != nil {
    555 		return nil, fmt.tprintf("finish: %v", ferr)
    556 	}
    557 	// Every value was read before the statement was finalized, so anything
    558 	// still pointing into SQLite's memory is now pointing at whatever took
    559 	// its place.
    560 	return read[:], ""
    561 }
    562 
    563 // expected_type is the storage class SQLite should give a bound value.
    564 @(private)
    565 expected_type :: proc(v: sqlite3.Value) -> sqlite3.Type {
    566 	switch bound in v {
    567 	case i64:
    568 		return .Integer
    569 	case bool:
    570 		return .Integer
    571 	case f64:
    572 		// A NaN has no SQLite representation, so it lands as NULL.
    573 		return math.is_nan(bound) ? .Null : .Real
    574 	case string:
    575 		return .Text
    576 	case []byte:
    577 		return .Blob
    578 	}
    579 	return .Null
    580 }
    581 
    582 // value generates one bound parameter, weighted towards the edges of each
    583 // type rather than the middle.
    584 value :: proc() -> sqlite3.Value {
    585 	switch rand.int_range(0, 6) {
    586 	case 0:
    587 		return integer()
    588 	case 1:
    589 		return real()
    590 	case 2:
    591 		return rand.int_range(0, 2) == 1
    592 	case 3:
    593 		return text()
    594 	case 4:
    595 		return bytes()
    596 	}
    597 	return nil
    598 }
    599 
    600 // integer generates an i64, often one of the values that overflow or sign
    601 // flip if a conversion is wrong somewhere.
    602 integer :: proc() -> i64 {
    603 	edges := []i64 {
    604 		0,
    605 		1,
    606 		-1,
    607 		127,
    608 		128,
    609 		255,
    610 		256,
    611 		-128,
    612 		-129,
    613 		65535,
    614 		65536,
    615 		2147483647,
    616 		2147483648,
    617 		-2147483648,
    618 		-2147483649,
    619 		max(i64),
    620 		min(i64),
    621 		max(i64) - 1,
    622 		min(i64) + 1,
    623 	}
    624 	if rand.int_range(0, 2) == 0 {
    625 		return rand.choice(edges)
    626 	}
    627 	return i64(rand.uint64())
    628 }
    629 
    630 // real generates an f64, including the values SQLite has no room for.
    631 real :: proc() -> f64 {
    632 	edges := []f64 {
    633 		0,
    634 		-0,
    635 		1,
    636 		-1,
    637 		0.1,
    638 		math.INF_F64,
    639 		math.NEG_INF_F64,
    640 		math.nan_f64(),
    641 		max(f64),
    642 		min(f64),
    643 		1e308,
    644 		1e-308,
    645 	}
    646 	if rand.int_range(0, 2) == 0 {
    647 		return rand.choice(edges)
    648 	}
    649 	return transmute(f64)rand.uint64()
    650 }
    651 
    652 // text generates a string from the bytes that break SQL built by hand, plus
    653 // multi-byte runes and, deliberately, byte sequences that are not UTF-8.
    654 text :: proc() -> string {
    655 	pieces := []string {
    656 		"'",
    657 		`"`,
    658 		"`",
    659 		";",
    660 		"--",
    661 		"/*",
    662 		"*/",
    663 		"\\",
    664 		"\n",
    665 		"\r",
    666 		"\t",
    667 		"\x00",
    668 		"%",
    669 		"_",
    670 		"?",
    671 		"$1",
    672 		":name",
    673 		"DROP TABLE t",
    674 		"' OR '1'='1",
    675 		"é",
    676 		"\U0001F600",
    677 		"\xff\xfe",
    678 		"a",
    679 		" ",
    680 		"",
    681 	}
    682 	n := rand.int_range(0, 24)
    683 	b := strings.builder_make(context.temp_allocator)
    684 	for _ in 0 ..< n {
    685 		strings.write_string(&b, rand.choice(pieces))
    686 	}
    687 	return strings.to_string(b)
    688 }
    689 
    690 // sql_shaped_text generates text that is trying hard to be mistaken for SQL.
    691 sql_shaped_text :: proc() -> string {
    692 	attacks := []string {
    693 		"'; DROP TABLE canary; --",
    694 		"' OR 1=1; --",
    695 		"'||(SELECT name FROM sqlite_schema)||'",
    696 		"\"; DROP TABLE canary; \"",
    697 		"'); DELETE FROM t; --",
    698 		"x'41'",
    699 		"' UNION SELECT * FROM canary --",
    700 		"?; DROP TABLE canary",
    701 		"$1; DROP TABLE canary",
    702 	}
    703 	if rand.int_range(0, 2) == 0 {
    704 		return rand.choice(attacks)
    705 	}
    706 	return strings.concatenate({rand.choice(attacks), text()}, context.temp_allocator)
    707 }
    708 
    709 // bytes generates a blob, which unlike text has no encoding to respect.
    710 bytes :: proc() -> []byte {
    711 	n := rand.int_range(0, 64)
    712 	out := make([]byte, n, context.temp_allocator)
    713 	_ = rand.read(out)
    714 	return out
    715 }
    716 
    717 // damaged_statement generates SQL the parser should refuse: a valid statement
    718 // with a piece cut out or a byte flipped, or simply a run of random bytes.
    719 damaged_statement :: proc() -> string {
    720 	valid := []string {
    721 		`SELECT 1`,
    722 		`CREATE TABLE z(a, b)`,
    723 		`INSERT INTO z VALUES (1, 2)`,
    724 		`SELECT a FROM z WHERE b = ?`,
    725 		`BEGIN`,
    726 		`PRAGMA journal_mode`,
    727 		// The LIMIT is load-bearing. A recursive CTE is worth feeding to
    728 		// the parser, but a damaged one can recurse without end, and a
    729 		// bound keeps this corpus entry from relying on the watchdog.
    730 		`WITH r(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM r WHERE n < 3) SELECT n FROM r LIMIT 4`,
    731 		`SELECT count(*) FROM sqlite_schema`,
    732 	}
    733 	switch rand.int_range(0, 4) {
    734 	case 0:
    735 		return text()
    736 	case 1:
    737 		// A byte flipped somewhere in the middle.
    738 		src := rand.choice(valid)
    739 		if len(src) == 0 {
    740 			return src
    741 		}
    742 		out := make([]byte, len(src), context.temp_allocator)
    743 		copy(out, src)
    744 		out[rand.int_range(0, len(out))] = byte(rand.int_range(0, 256))
    745 		return string(out)
    746 	case 2:
    747 		// Truncated at a random point, which is where a parser that reads
    748 		// past its input would fall off.
    749 		src := rand.choice(valid)
    750 		return src[:rand.int_range(0, len(src) + 1)]
    751 	}
    752 	return strings.concatenate({rand.choice(valid), text()}, context.temp_allocator)
    753 }
    754 
    755 // show renders a value so a failure can be read and retyped, with the bytes
    756 // spelled out rather than printed raw.
    757 show :: proc {
    758 	show_value,
    759 	show_string,
    760 	show_bytes,
    761 }
    762 
    763 show_value :: proc(v: sqlite3.Value) -> string {
    764 	switch bound in v {
    765 	case i64:
    766 		return fmt.tprintf("i64(%d)", bound)
    767 	case f64:
    768 		return fmt.tprintf("f64(%v / %08x)", bound, transmute(u64)bound)
    769 	case bool:
    770 		return fmt.tprintf("bool(%v)", bound)
    771 	case string:
    772 		return show_string(bound)
    773 	case []byte:
    774 		return show_bytes(bound)
    775 	}
    776 	return "nil"
    777 }
    778 
    779 show_string :: proc(s: string) -> string {
    780 	return fmt.tprintf("string(%d bytes, %02x)", len(s), transmute([]byte)s)
    781 }
    782 
    783 show_bytes :: proc(b: []byte) -> string {
    784 	return fmt.tprintf("blob(%d bytes, %02x)", len(b), b)
    785 }