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 }