mirror of
https://github.com/Vateron-Media/XC_VM.git
synced 2026-09-26 12:01:57 +02:00
- constants stub: use mt_rand()-based exprs so PHPStan infers GENERAL types,
not literal 0/'' — fixes false division-by-zero (PACKET_SIZE) and
foreach-over-false (str_split with len 0). Load stub via bootstrapFiles so
result-cache invalidates on change.
- return contracts: explicit returns where a path fell through to null and
violated the declared type:
- StreamRepository::getById/getWatchFolder, GroupService::getById,
getStream() → return false (declared array|false).
- ServerRepository::getPublicURL → return '' when server missing (array→string).
- MagService::resetSTB → return query() result (declared bool).
- StreamUtils::getPlaylistSegments → explicit return null.
- NetworkUtils::stopDownload → @return null corrected to @return void.
- PlexController: getPlexToken() called with 5 args but accepts 4 — dropped
the dead 5th argument.
- DropboxClient::getMetaFromHeaders: array_shift() on an array_filter()
expression (not a variable, by-ref error) — assign to a var first.
- WatchdogCommand: wrap numeric-string subtractions (nginx/proc-stat values)
in floatval()/intval().
116 lines
4.2 KiB
PHP
116 lines
4.2 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Generates tools/phpstan/constants.stub.php by scanning every define() call in
|
|
* src/ and inferring a rough type for each constant. Run from the project root:
|
|
*
|
|
* php tools/phpstan/gen-constants-stub.php > tools/phpstan/constants.stub.php
|
|
*
|
|
* @package XC_VM
|
|
*/
|
|
|
|
$root = dirname(__DIR__, 2) . '/src';
|
|
$dir = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS));
|
|
$consts = [];
|
|
|
|
foreach ($dir as $f) {
|
|
if ($f->getExtension() !== 'php') {
|
|
continue;
|
|
}
|
|
$src = @file_get_contents($f->getPathname());
|
|
if ($src === false || strpos($src, 'define(') === false) {
|
|
continue;
|
|
}
|
|
$tokens = token_get_all($src);
|
|
$n = count($tokens);
|
|
for ($i = 0; $i < $n; $i++) {
|
|
$t = $tokens[$i];
|
|
if (!(is_array($t) && $t[0] === T_STRING && $t[1] === 'define')) {
|
|
continue;
|
|
}
|
|
$name = null;
|
|
$k = $i + 1;
|
|
for (; $k < $n && $k < $i + 40; $k++) {
|
|
$tk = $tokens[$k];
|
|
if (is_array($tk) && $tk[0] === T_CONSTANT_ENCAPSED_STRING) {
|
|
$name = trim($tk[1], "'\"");
|
|
break;
|
|
}
|
|
if ($tk === ')') {
|
|
break;
|
|
}
|
|
}
|
|
if (!$name || !preg_match('/^[A-Z_][A-Z0-9_]*$/', $name) || isset($consts[$name])) {
|
|
continue;
|
|
}
|
|
$hasIntLit = $hasStrLit = $hasConcat = $hasBool = false;
|
|
$isIntOnly = true;
|
|
$seen = false;
|
|
$d = 0;
|
|
for ($m = $k + 1; $m < $n && $m < $k + 80; $m++) {
|
|
$tm = $tokens[$m];
|
|
if ($tm === '(') { $d++; continue; }
|
|
if ($tm === ')') { if ($d === 0) break; $d--; continue; }
|
|
if ($tm === ',' && $d === 0) { $seen = true; continue; }
|
|
if (!$seen) { continue; }
|
|
if (is_array($tm)) {
|
|
if ($tm[0] === T_LNUMBER || $tm[0] === T_DNUMBER) { $hasIntLit = true; }
|
|
elseif ($tm[0] === T_CONSTANT_ENCAPSED_STRING) { $hasStrLit = true; $isIntOnly = false; }
|
|
elseif ($tm[0] === T_STRING && in_array(strtolower($tm[1]), ['true', 'false'], true)) { $hasBool = true; $isIntOnly = false; }
|
|
elseif ($tm[0] === T_WHITESPACE) { /* skip */ }
|
|
else { $isIntOnly = false; }
|
|
} else {
|
|
if ($tm === '.') { $hasConcat = true; $isIntOnly = false; }
|
|
else { $isIntOnly = false; }
|
|
}
|
|
}
|
|
if ($hasConcat || $hasStrLit) { $type = 'string'; }
|
|
elseif ($hasBool && !$hasIntLit) { $type = 'bool'; }
|
|
elseif ($hasIntLit && $isIntOnly) { $type = 'int'; }
|
|
else { $type = 'mixed'; }
|
|
$consts[$name] = $type;
|
|
}
|
|
}
|
|
|
|
// Explicit type overrides for constants whose value the heuristic cannot
|
|
// classify (defined from variables or function calls). Keeps the stub accurate
|
|
// and prevents false null/mixed positives at call sites.
|
|
$overrides = [
|
|
'HOST' => 'string', // trim(explode(':', HTTP_HOST)[0])
|
|
'PAGE_NAME' => 'string',
|
|
'PHP_ERRORS' => 'bool', // dev-mode flag
|
|
];
|
|
foreach ($overrides as $name => $type) {
|
|
if (isset($consts[$name])) {
|
|
$consts[$name] = $type;
|
|
}
|
|
}
|
|
|
|
ksort($consts);
|
|
|
|
echo "<?php\n\n";
|
|
echo "/**\n";
|
|
echo " * PHPStan constants stub for XC_VM (auto-generated by gen-constants-stub.php).\n";
|
|
echo " *\n";
|
|
echo " * The application defines these constants at RUNTIME inside methods, where\n";
|
|
echo " * PHPStan cannot see them via static scanning. Values are a cast of an unset\n";
|
|
echo " * env var so PHPStan gets the correct GENERAL type without narrowing to a\n";
|
|
echo " * literal (which would cause false always-true/false comparison reports).\n";
|
|
echo " *\n";
|
|
echo " * @package XC_VM\n";
|
|
echo " */\n\n";
|
|
|
|
foreach ($consts as $name => $type) {
|
|
// Use mt_rand()-based expressions so PHPStan infers a GENERAL type
|
|
// (int/string/bool) and never constant-folds to a literal (0/'') — a
|
|
// literal would create false positives like division-by-zero or
|
|
// always-false comparisons at call sites.
|
|
$expr = match ($type) {
|
|
'string' => "(string) mt_rand()",
|
|
'int' => "mt_rand()",
|
|
'bool' => "(bool) mt_rand()",
|
|
default => "json_decode((string) mt_rand(), true)",
|
|
};
|
|
echo "if (!defined('$name')) define('$name', $expr);\n";
|
|
}
|