返回资讯中心
外部精选
软件工程
#开发者实时流

Vimeo 发布 PHP 静态分析工具 Psalm 7.0.0

来源标题:Psalm 7.0.0 [PHP static analysis tool by Vimeo]

Psalm 7.0.0 正式发布,带来大量新功能和缺陷修复。该版本要求运行在 64 位 PHP 8.2 至 8.6 上,多数项目升级流程简单;污点分析改用位掩码存储并基于函数紧凑摘要解析污点图,安全分析速度最高提升 10 倍。

Hacker News 最新petecooper2026/11/2 · 今天26 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

Psalm 7 发布公告!

本次发布包含大量新功能和错误修复,例如:

Psalm 7 可在 64 位 PHP 8.2 至 8.6 上运行。对大多数项目来说升级很简单:请参阅下方的“升级”部分。

污点现在以位掩码形式存储,污点图基于每个函数的紧凑摘要来解析。这使得安全分析速度提升最高可达 10 倍。

组合分析(#11384)在一次运行中同时执行类型检查、安全分析和死代码检测,因此污点分析现在默认开启(#11399)。

污点问题以 SECURITY 严重级别报告,并在 SARIF 报告中带有 rank(#12068、#11383)。结果不再因运行次数或线程数而变化(#11946、#11952、#11953)。

<?php
$run = function (string $c): void {
    shell_exec($c);
};
$run((string) $_GET['cmd']); // TaintedShell,经由闭包传递

$mongo = new MongoDB\Driver\Manager('mongodb://localhost');
$mongo->executeQuery('db.users', new MongoDB\Driver\Query(['name' => $_GET['name']])); // TaintedNosql

/** @return mixed */
function body() {
    return json_decode((string) file_get_contents('php://input'), true);
}
eval((string) body()); // TaintedEval:php://input 是污点源

在 psalm.dev 上试用 »

@psalm-pure、@psalm-mutation-free 和 @psalm-external-mutation-free 被 capabilities 取代,其建模参考了 Hack 的 contexts 和 capabilities。函数式声明会明确指定它可能产生哪些副作用:read-props、write-this-props、write-props、read-globals、write-globals、write-refs、io 和 time,两端分别是 pure 和 impure(#11984):

<?php
/** @psalm-capabilities time */
function today(): string {
    return date('Y-m-d'); // 无问题
}

/** @psalm-pure */
function todayWrong(): string {
    return date('Y-m-d'); // ImpureFunctionCall:... 需要 time
}

在 psalm.dev 上试用 »

新的 Purity Model 文章完整解释了该模型。

许多函数的纯度取决于传入的参数:当回调是纯的时,array_map() 就是纯的。纯度模板是模板参数,其值是 capability 集合而非类型,类似 Hack 的 [ctx $f] contexts。函数现在可以既是纯的又是高阶的,每次调用的代价仅取决于其参数的行为(#11984)。

借助 Closure[_],函数从其接收的闭包中获得纯度:

<?php
/**
 * @psalm-pure
 * @param Closure[_](int): int $f
 */
function apply(Closure $f, int $x): int {
    return $f($x);
}

/** @psalm-pure */
function double(int $x): int {
    return apply(fn(int $y): int => $y * 2, $x); // 无问题:闭包是纯的
}

/** @psalm-pure */
function noisy(int $x): int {
    return apply(function (int $y): int { echo $y; return $y; }, $x); // ImpureFunctionCall:上下文是纯的,但对 apply 的调用需要 io
}

/** @psalm-pure */
function lengths(array $words): array {
    return array_map(fn(string $w): int => strlen($w), $words); // 无问题:接收回调的内置函数同理
}

在类上,纯度模板让每个子类决定其方法可以做什么:

<?php
/** @psalm-purity-template P */
abstract class Handler {
    /**
     * @psalm-pure
     * @psalm-purity-from-template P
     */
    abstract public function handle(string $input): string;
}

/** @extends Handler[pure] */
final class Upper extends Handler {
    /** @psalm-pure */
    #[Override]
    public function handle(string $input): string {
        return strtoupper($input);
    }
}

/** @extends Handler[io] */
final class Printer extends Handler {
    /** @psalm-capabilities io */
    #[Override]
    public function handle(string $input): string {
        echo $input;
        return $input;
    }
}

/** @psalm-pure */
function viaUpper(Upper $handler): string {
    return $handler->handle('a'); // 无问题:Upper 将 P 绑定为 pure
}

/** @psalm-pure */
function viaPrinter(Printer $handler): string {
    return $handler->handle('a'); // ImpureMethodCall:上下文是纯的,但方法 Printer::handle 需要 io
}

Traversable、Iterator、IteratorAggregate 和 Generator 都有一个纯度模板,用于描述遍历它们时的行为。iterable 也有一个。带有纯度注解的生成器会将其绑定到其函数体的行为:

<?php
/**
 * @psalm-pure
 * @param Traversable[pure]<int, int> $xs
 */
function sum(Traversable $xs): int {
    $total = 0;
    foreach ($xs as $x) { // 无问题
        $total += $x;
    }
    return $total;
}

/**
 * @psalm-pure
 * @param Traversable<int, int> $xs
 */
function sumAny(Traversable $xs): int {
    $total = 0;
    foreach ($xs as $x) { // ImpureMethodCall:上下文是纯的,但遍历 Traversable<int, int> 需要 impure
        $total += $x;
    }
    return $total;
}

/**
 * @psalm-pure
 * @return Generator<int, int>
 */
function numbers(): Generator {
    yield 1;
    yield 2;
}

/** @psalm-pure */
function total(): int {
    return sum(numbers()); // 无问题:numbers() 返回 Generator[pure]
}

语法:纯度参数写在类型参数之前的方括号中,与 Hack 一致:Closureio: void、Handler[pure]、Iterator[pure]<int, string>、Box[io]<int>。具名模板用 @psalm-purity-template P 声明,用 @psalm-purity-from-template P 使用。

边界和默认值以链式写法表示:@psalm-purity-template write-this-props <= C(write-this-props) <= write-this-props|write-props|io。方法可以无条件使用下界。子类必须保持在边界之内,未绑定模板时则获得默认值。

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →