Perl Hackers Hub

第27回 Perlにおける静的解析(1)

この記事を読むのに必要な時間:およそ 3 分

Perlの静的解析を支える技術

Perlの静的解析を実現する方法はいくつかあります。ここではその中からPerlの静的解析を支える代表的な技術を紹介します。

PPIを使う方法

先述したPPIは,2005年に初の安定版がリリースされ,現在ではバージョン1.213がリリースされています。

PPIの特徴

PPIはコードがPerlで記述されているため,多くのプラットフォーム上で安定して動作します。それに加えて,CPANにアップロードされているモジュールの99%以上を正しく解析できたという実績と信頼があるモジュールです。

一方でピュアPerlな実装であるため,先述したCompiler::LexerなどのC++で記述された解析器と比較すると実行速度に劣るというデメリットもあります。ただ,それを補って余りある信頼性の高さから,Perlの静的解析の分野では広く使用されています。

PPIによるソースコードの解析

PPIによるPerlのソースコード解析は大きく2つのステップに分割できます。1つ目のステップはPPI::Tokenizerを使ってソースコードをトークン列に分解する作業です。続く2つ目のステップはPPI::LexerあるいはPPI::Documentを用いて,PDOMと呼ばれる抽象構文木をPerl向け(と言うよりもPPI向け)に拡張したデータ構造に変換する作業です。PPI::LexerはPPI::Tokenizerによって得られるトークン列から,PPI::Documentは対象とするソースコードのファイルパスをからそれぞれPDOMを生成します。以下にコード例を示します。

トークン列を取得するプログラム

use PPI;

# トークナイザのインスタンスを作る
my $tokenizer = PPI::Tokenizer->new('/path/to/file.pl');
my $tokens = $tokenizer->all_tokens(); # トークン列を得る

PPI::LexerでPDOMを取得するプログラム

use PPI;

# トークナイザのインスタンスを作る
my $tokenizer = PPI::Tokenizer->new('/path/to/file.pl');
# PPI::Lexer のインスタンスを作る
my $lexer = PPI::Lexer->new();
my $pdom = $lexer->lex_tokenizer($tokenizer); # PDOM を得る

PPI::DocumentでPDOMを取得するプログラム

use PPI;

my $pdom = PPI::Document->new('/path/to/file.pl');
                                              # PDOM を得る

なお,PDOMに変換する処理は,内部的にはPPI::Tokenizerでトークナイズして得られたトークン列を処理することで実現しています。解析に際してPDOMが必要とならない場合,つまりトークン列のみで十分な場合にPPI::Lexerなどを使うとオーバーヘッドの原因となるので,状況に応じた使い分けが必要となってくるでしょう。詳細に関してはPPIのPerldocを参照してください。

PPI::XSは使わないように!

CPANには,PPI::XSと呼ばれるXSによる高速化版を謳(うた)うモジュールも存在します。しかし,長らくメンテナンスがされていないことや,XSで提供されている部分は実は定数の返却の部分のみで,ほかの部分はピュアPerlの実装と何ら変わりがなく,高速に動作することはほぼありえないという理由から,積極的に採用する理由は皆無でしょう。

Compiler::LexerおよびCompiler::Parserを使う方法

先述したCompiler::LexerとCompiler::Parserはそれぞれ,Perlのソースコードをトークン列に分割する処理と,そのトークン列を抽象構文木に変換する処理を行います。

Compiler::*の特徴

両モジュールはC++で記述されており,前述のPPIと比較して非常に高速に動作します。また,豊富なテストによって機能が担保されていることや,最近ではPPIで書かれていたモジュールをCompiler::Lexerで再実装したモジュールが利用されていることなどから,信頼性についても問題のない水準に達していると言えるでしょう。

Compiler系のモジュールを使うことによる高速化の実例を挙げると,PPIを利用して書かれたPerl::MinumumVersionとCompiler::Lexerにより書かれたPerl::MinimumVersion::Fastの速度差を比較すると後者のほうが30倍程度高速に動作するといったものが挙げられます。これだけでも使う動機としては十分でしょう。

Compiler::*によるソースコードの解析

Compiler::LexerによりPerlで書かれたソースコードをトークン列に変換し,そのトークン列をCompiler::Parserで抽象構文木に変換するという一連の流れを記述したコード例を示します。

use Compiler::Lexer;
use Compiler::Parser;

# ソースコードの文字列を読み込む
open my $fh, "<", '/path/to/file.pl';
my $src = do { local $/; <$fh> };

# トークン列を得る
my $lexer = Compiler::Lexer->new($filename);
my $tokens = $lexer->tokenize($src);

# トークン列から抽象構文木を得る
my $parser = Compiler::Parser->new();
my $ast = $parser->parse($tokens);

詳細については各モジュールに関するPerldocを参照してください。

<続きの(2)こちら。>

著者プロフィール

川上大喜(かわかみたいき)

北海道函館市出身。高専卒業後,大学に編入。現在,大学院生業務をこなしつつ某企業でパートタイムエンジニアとして活動中。ソフトウェアのテストやCIといった,ソフトウェアの品質担保に関する話題に強い興味を持っている。

Twitter:@moznion
Web:http://moznion.hatenadiary.com/