再演: 「PHP7で堅牢なコードを書く - 例外処理、表明プログラミング、契約による設計」 + αという勉強会で、「assertぐらいでエバルんじゃねえ!」というふざけたタイトルで発表してきました。
資料はこちら。
PHP7では内部的にASTを作るようになりまして、それをPHP側から使えるようにするphp-astというC拡張があります。これを使って型推論つきの静的解析をするツールがPhanです。
Phanでは未定義変数や型に関する間違いを警告してくれるのですが、そういう明らかなバグの他にも自前のプラグインを作ってエラーをチェックすることができます。
スライドの趣旨としては、assertの話から入るものの、assertのことが主題ではなく、Phanを使ってコードの自動チェックを充実させようという内容です。
Phanプラグインの作り方
Phanプラグインの書き方は一応ドキュメントがあるのですが、詳しくないのでコードを読む必要があります。
今回のスライドで使ったプラグインの場合は、プロジェクトの .phan/plugins
ディレクトリに次のようなファイルを置いて、
<?php declare(strict_types=1);
# .phan/plugins/NonBoolAssertPlugin.php
use Phan\AST\AnalysisVisitor;
use Phan\CodeBase;
use Phan\Language\Context;
use Phan\Language\UnionType;
use Phan\Plugin;
use Phan\Plugin\PluginImplementation;
use ast\Node;
use Phan\Issue;
class NonBoolAssertPlugin extends PluginImplementation {
public function analyzeNode(
CodeBase $code_base,
Context $context,
Node $node,
Node $parent_node = null
) {
(new NonBoolAssertVisitor($code_base, $context, $this))(
$node
);
}
}
class NonBoolAssertVisitor extends AnalysisVisitor {
/** @var Plugin */
private $plugin;
public function __construct(
CodeBase $code_base,
Context $context,
Plugin $plugin
) {
parent::__construct($code_base, $context);
$this->plugin = $plugin;
}
public function visit(Node $node)
{
}
public function visitCall(Node $node) : Context
{
$expression = $node->children['expr'];
if ($expression->kind === \ast\AST_NAME &&
$expression->children['name'] === 'assert'
) {
$union_type = UnionType::fromNode($this->context, $this->code_base, $node->children['args']->children[0]);
if ($union_type->serialize() !== "bool") {
$this->plugin->emitIssue(
$this->code_base,
$this->context,
'PhanPluginNonBoolAssert',
"Non bool value passed to assert"
);
}
}
return $this->context;
}
}
return new NonBoolAssertPlugin;
.phan/config.php
にこのような記述を追加すれば良いです。
'plugins' => [
'.phan/plugins/NonBoolAssertPlugin.php',
],