packages/sql-catalog/fuzz/Target/AnalyzeTarget.php
1<?php
2
3declare(strict_types=1);
4
5namespace Fuzz\Target;
6
7use Error;
8use SqlCatalog\Facade\Analyzer;
9
10/**
11 * Feeds arbitrary bytes to the analyzer as if they were a source file.
12 *
13 * The analyzer is pointed at whatever a repository happens to contain, so it has
14 * to survive anything: a file that is not PHP, a file that is half PHP, a file
15 * whose statements nest deeper than anyone would write. Nothing it is given may
16 * make it throw; a file it cannot read is reported, not raised.
17 */
18final class AnalyzeTarget
19{
20 private readonly Analyzer $analyzer;
21
22 /**
23 * Builds the target over an analyzer with the built-in extensions.
24 */
25 public function __construct()
26 {
27 $this->analyzer = new Analyzer();
28 }
29
30 /**
31 * Analyzes the input and checks that the catalog it produced is coherent.
32 *
33 * @throws Error When the catalog contradicts itself
34 */
35 public function __invoke(string $input): void
36 {
37 $catalog = $this->analyzer->analyzeSource(['fuzz.php' => '<?php ' . $input]);
38
39 foreach ($catalog as $entry) {
40 if ($entry->id === '' || $entry->site->file !== 'fuzz.php') {
41 throw new Error('Catalogued a statement without an identity; input=' . bin2hex($input));
42 }
43 if ($entry->isExact() && str_contains($entry->sql(), '{$}')) {
44 throw new Error('A resolved statement still shows a gap; input=' . bin2hex($input));
45 }
46 }
47
48 if ($catalog->count() > 0 && $catalog->problems() !== []) {
49 throw new Error('Reported both statements and a parse failure; input=' . bin2hex($input));
50 }
51 }
52}
53