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