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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
<?php
declare(strict_types=1);
namespace Shirabe\PluginClassifier;
final class Report
{
public function __construct(private readonly Classifier $classifier)
{
}
public function writeJson(string $path): void
{
$vendor = [];
foreach ($this->classifier->closure->vendorReachable as $fqcn => $bits) {
$info = VendorPackages::lookup($fqcn);
$vendor[] = [
'fqcn' => $fqcn,
'package' => $info['package'],
'category' => $info['category'],
'direction' => $bits === 3 ? 'both' : ($bits === 1 ? 'provided' : 'consumed'),
];
}
usort($vendor, static fn (array $a, array $b) => strcmp($a['fqcn'], $b['fqcn']));
$builtins = array_keys($this->classifier->closure->builtinReachable);
sort($builtins);
$vendorPackages = [];
foreach (VendorPackages::PREFIXES as $prefix => $info) {
$vendorPackages[$info['package']] = $info['category'];
}
ksort($vendorPackages);
$data = [
'categories' => $this->countByCategory(),
'classes' => array_values($this->classifier->rows),
'vendorPackages' => $vendorPackages,
'vendorTypes' => $vendor,
'reachableBuiltins' => $builtins,
'violations' => $this->classifier->violations,
];
$json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
if (file_put_contents($path, $json . "\n") === false) {
throw new \RuntimeException("cannot write $path");
}
}
public function printSummary(): void
{
$counts = $this->countByCategory();
echo "# Plugin boundary classification\n\n";
echo "| category | classes |\n|---|---|\n";
foreach ($counts as $category => $count) {
echo "| $category | $count |\n";
}
echo "\n## Reachable surface\n\n";
foreach (['rust-proxy', 'rust-snapshot', 'contract'] as $category) {
$names = [];
foreach ($this->classifier->rows as $row) {
if ($row['category'] === $category && $row['reachable']) {
$names[] = $row['fqcn'] . ($row['direction'] === 'both' ? ' (both)' : '');
}
}
echo '### ' . $category . ' (' . count($names) . ")\n\n";
foreach ($names as $name) {
echo "- $name\n";
}
echo "\n";
}
$overridden = array_filter($this->classifier->rows, static fn (array $r) => isset($r['computedCategory']));
if ($overridden !== []) {
echo "## Overridden\n\n";
foreach ($overridden as $row) {
echo "- {$row['fqcn']}: {$row['computedCategory']} -> {$row['category']}\n";
}
echo "\n";
}
if ($this->classifier->violations !== []) {
echo "## Violations\n\n";
foreach ($this->classifier->violations as $violation) {
echo "- $violation\n";
}
echo "\n";
}
}
/** @return array<string, int> */
private function countByCategory(): array
{
$counts = array_fill_keys(Classifier::CATEGORIES, 0);
foreach ($this->classifier->rows as $row) {
$counts[$row['category']]++;
}
return $counts;
}
}
|