aboutsummaryrefslogtreecommitdiffhomepage
path: root/scripts/linters/src/Linters/NoHaltCompilerLiteral.php
blob: 301608863dc4af1d2799c29a99de7b54f6068376 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<?php

declare(strict_types=1);

namespace Shirabe\Lint\Linters;

use Shirabe\Lint\Linter;
use Shirabe\Lint\Support\FileFinder;
use Shirabe\Lint\Support\Paths;

final class NoHaltCompilerLiteral implements Linter
{
    // Assembled so that this file does not contain what it looks for.
    private const TOKEN = '__' . 'HALT_COMPILER();';

    public function name(): string
    {
        return 'no_halt_compiler_literal';
    }

    public function failureIntro(): string
    {
        return "Found a literal `" . self::TOKEN . "` (case-insensitive).\n"
            . "The executable carries the Composer runtime bundle as a phar that PHP finds by\n"
            . "scanning for the first occurrence of that token, and every literal here ends up in\n"
            . "the same binary, so an earlier one shadows the bundle. Build the token at run time\n"
            . "instead — see "
            . '`halt_compiler_token` in `crates/shirabe-php-shim/src/phar.rs`:';
    }

    public function check(string $rootDir, array $excludes): array
    {
        $errors = [];

        foreach ($this->embeddedFiles($rootDir) as $path) {
            $relative = Paths::relativeTo($rootDir, $path);
            if (in_array($relative, $excludes, true)) {
                continue;
            }

            foreach (file($path) as $idx => $raw) {
                if (stripos($raw, self::TOKEN) === false) {
                    continue;
                }

                $errors[] = "{$relative}:" . ($idx + 1) . ': ' . trim($raw);
            }
        }

        return $errors;
    }

    /**
     * The sources whose bytes reach the binary: Rust code, and the PHP files the Rust code
     * embeds with `include_str!`.
     *
     * @return list<string>
     */
    private function embeddedFiles(string $rootDir): array
    {
        $paths = FileFinder::rustFiles($rootDir);

        foreach (glob("{$rootDir}/crates/*/php", GLOB_ONLYDIR) ?: [] as $phpDir) {
            $iterator = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($phpDir, \FilesystemIterator::SKIP_DOTS),
            );
            foreach ($iterator as $file) {
                /** @var \SplFileInfo $file */
                if ($file->isFile()) {
                    $paths[] = $file->getPathname();
                }
            }
        }

        sort($paths);

        return $paths;
    }
}