Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

CPANの依存モジュールをもう少し正しく検出したい

1,990 views

Published on

YAP(achimon)C::Asia Hachioji 2016 mid in Shinagawa

Published in: Software
  • Be the first to comment

CPANの依存モジュールをもう少し正しく検出したい

  1. 1. CPAN の依存モジュール をもう少し正しく検出し たい Kenichi Ishigaki (@charsbar) July 3, 2016 YAP(achimon)C::Asia Hachioji 2016mid
  2. 2. Perl QA Hackathon • April 21-24 @ Rugby, UK http://act.qa-hackathon.org/qa2016/sponsors.html • PAUSE • CPANTS https://www.flickr.com/photos/jj_perl/26591371285/in/album-72157667398548526/
  3. 3. PAUSE • CPAN モジュールの登録・管 理 • 簡単な事前審査や静的解析 も • 昨年から進めていた Plack へ の移行は完了 • さらに近代化を進めたい
  4. 4. CPANTS • 第三者目線のテスト (<-> CPAN::Testers) • 体裁が適切かどうか ( 静的解析 ) • 2012 年に管理を引き継ぎ • いろいろ問題が出てきていたので全面 的にリファクタリング • 更新間隔が 1 日から 15 分に
  5. 5. prereq_matches_use • 必要な依存モジュールが適切に宣言さ れているか ( インストールには成功するんだけど場合に よっては使うときに Can't locate FooBar.pm in @INC エラーが出るようなモジュールを検出 ) • マシンを買い換えたとか Perl のバー ジョンを上げたらコケるようになった というときに真っ先にチェックしたい 項目のひとつ • 静的インストールの検証にも
  6. 6. prereq_matches_use package Archive::Any::Lite; use strict; use warnings; use File::Spec; ... package Archive::Any::Lite::Tar; use Archive::Tar; ... package Archive::Any::Lite::Zip; use Archive::Zip … requires: Archive::Tar: '1.76' Archive::Zip: '0' File::Spec: '0' File::Temp: '0.19' IO::Uncompress::Bunzip2: '0'
  7. 7. prereq_matches_use 古くからあるコアモジュールは省略可能 package Archive::Any::Lite; use strict; use warnings; use File::Spec; ... package Archive::Any::Lite::Tar; use Archive::Tar; ... package Archive::Any::Lite::Zip; use Archive::Zip … requires: Archive::Tar: '1.76' Archive::Zip: '0' File::Spec: '0' File::Temp: '0.19' IO::Uncompress::Bunzip2: '0'
  8. 8. prereq_matches_use 現状では 2 割くらいに問題がある •META の更新漏れ •新しいコアモジュールの指定漏れ •残念ながら CPANTS 側に問題があること も
  9. 9. Module::ExtractUse • Thomas Klausner さん作 (2003 年~ 2008 年、 2012 年~ ) • 不要なものを除去したあとセミコロン 単位で行を分割 • use などのキーワードを見つけたら再帰 下降パーサ (Parse::RecDescent) で解析
  10. 10. たいていの場合は これで十分
  11. 11. Moose の仲間には未対応 ちょうど開発中断していた頃に活発 化 •Moose (2006 年~ ) •Mouse (2008 年~ ) •Moo (2010 年~ )
  12. 12. ヒアドキュメントの 中身を見てしまう my $template = <<'END'; use Module; ... END
  13. 13. 環境依存のモジュールを 無視してくれない use Test::More; BEGIN { if ($^O ne 'MSWin32') { plan skip_all => 'for Win32 only'; } } use Win32::Module; ...
  14. 14. ヒアドキュメントや plan skip_all の後も除去? CPANTS 的には取り過ぎるよりは取れ ない方が害は少ない ( 表に出てこない高速化モジュールな どがあるので、 META 側の情報が多 い分には問題ない )
  15. 15. META の自動生成などに は使いづらくなる BEGIN { if ($^O ne 'MSWin32') { plan skip_all => 'for Win32 only'; } } # Win32 環境では入れてほしい use Win32::Module;
  16. 16. どこまで正確さを 求めるかも悩まし い
  17. 17. 厳密に言うなら BEGIN の有無もチェック必要 BEGIN { if ($^O ne 'MSWin32') { plan skip_all => 'for Win32 only'; } } use Win32::Module;
  18. 18. 本気で解析するなら、 セミコロン分割から直すべ き BEGIN { use Test::More; if ($^O ne 'MSWin32') { plan skip_all => 'for Win32 only'; } } use Win32::Module;
  19. 19. ハッカソンの枠で は収まりそうにな い 他にもいろいろな課題が残っていたので …ついつい後回しに
  20. 20. プロファイルを取ってみ た ファイルまわりの問題が出てくることを期待し ていた
  21. 21. Pod::Checker 分析部分だけ取り出してみ る Module::ExtractUse Parse::Local Distribuiton
  22. 22. Pod::Checker 分析部分だけ取り出してみ る Module::ExtractUse Parse::Local Distribution Parse::RecDescent Pod::Strip
  23. 23. Pod::Checker Module::ExtractUse Parse::Local Distribution Parse::RecDescent Pod::Strip どう見ても次のターゲット …は
  24. 24. 高速化だけで お茶を濁す手も • Pod::Strip を外す • 毎回パーサを生成する必要はないはず • 再帰下降パーサを別のものに切り替え る (Parse::RecDescent は AUTOLOAD を使って いるので遅くなりがち ) • これだけでも新しいテストの検証などは楽に なる
  25. 25. 見てしまったからに は 仕方ない いまやらなかったら、 次いつ直せるの?
  26. 26. 代わりになりそうな ものを探すことにし た 当然ですよね
  27. 27. ふたつの異なる要件 サーバ側 •Module::ExtractUse と同程度の速さはほしい •fork() などに対応していて欲しい •Perl のバージョンは気にしない ユーザ側 •速さは気にしない •Test::Kwalitee 対応するなら Perl 5.8.1 が必須 (Lancaster Consensus) •現状テストには DB が必要ということになって いるので、サイト専用のテストに移行する手も
  28. 28. Perl::PrereqScanner • Ricardo Signes さん作 (2010 年~ ) • Dist::Zilla から派生 • PPI ベース (2001 年~ ) • Moose 、 if 、 VERSION メソッドなどに も対応 • リポジトリには追加プラグインの PR も
  29. 29. Perl::PrereqScanner • 事実上の評価基準 : これでできること ができないとツライ • 遅すぎて大量のバッチ処理には向かな い ( 当初はこれしかなかったから移行を断 念 )
  30. 30. Perl::PrereqScanner::Lite • moznion さん作 (2013 年~ ) • WEB+DB PRESS Vol.81/82 (Perl Hackers Hub 第 27/28 回 ) • Compiler::Lexer ベース (goccy さん作 ) • 非常に高速 (P::PS の 20 ~ 30 倍 ) • Moose と VERSION メソッドに対応 • App::scan_prereqs_cpanfile などで利用
  31. 31. Perl::PrereqScanner::Lite I have to redesign and reimplement this module, and I have some plans. (GH#13)
  32. 32. Perl::PrereqScanner::Lite • Compiler::Lexer が不安定 ( 大量処理時に SEGV) • 拡張性の問題 ( トークンをそのまま処理している ) • 今年に入ってから FAIL が多発 (GH#15) ( おそらく version モジュールの変更によるも の )
  33. 33. Perl::PrereqScanner:: NotQuiteLite • 拙作 (2013 年~ ) • Compiler::Lexer ベース • 生トークンは隠蔽 • Perl::PrereqScanner と Module::ExtractUse のテストを通るように改造 • Plack::Builder にも対応
  34. 34. Perl::PrereqScanner:: NotQuiteLite • 個人的なモジュール管理には重宝 • これも大量処理時には不安定 • Compiler::Lexer …を差し替えたい
  35. 35. Perl::Lexer • tokuhirom さん作 (2013 年~ ) • Perl 本体の解析器を利用 • ( 理屈としては ) これ以上正しいものは ない • 同期が取れなくなる心配もない
  36. 36. Perl::Lexer THIS LIBRARY IS WRITTEN FOR RESEARCHING PERL5 LEXER API. THIS MODULE USES PERL5 INTERNAL API. DO NOT USE THIS.
  37. 37. Perl::Lexer • 繰り返し使えるようには ( まだ ) なって いない • Perl 5.18.1 → 5.10.0 • Devel::Declare の手法が参考にできるか も
  38. 38. Text::Balanced • Damian Conway さん作 (1997 年~ ) • Perl 5.7.3 から標準添付 • 文字列、括弧、正規表現などを ( 正規表 現で ) 取り出せる • Parse::RecDescent の中でも使われている
  39. 39. Text::Balanced • 開発は停滞 ( 実質的には 2006 年で止まっている ) • 最近の Perl についていけていない箇所 も (Perl 5.14 で導入された修飾子など ) • 括弧の対応がとれていないものに弱い • 補助的なたたき台としては使えそう
  40. 40. Perl::Tokenizer • スライドを書いている最中に発見 • Daniel Șuteu さん作 (2015 年~ ) • 正規表現ベース • 内部で状態管理 • 非常に高精度 • …書き始める前に見つけていたら
  41. 41. Perl::Tokenizer • Perl 5.18 • 解釈の部分は自前で実装必要 • 効率面でもベンチマークで確認必要
  42. 42. すぐに代用できる …ものはなさそう
  43. 43. というわけで、 作ることにした
  44. 44. 作成方針 • トークンは(基本的には)正規表現で 抽出 • 正規表現などの塊の抽出はひとまず Text::Balanced のコードを流用 • スコープの情報などは適宜状態変数に 格納していく
  45. 45. 頑張りすぎない • 欲しいのは依存モジュールの解析器 • モジュール名とバージョンを取れれば いい • 変な書き方まで検出できる必要はない print "@{[require Module]}"; s/YA8C/use Module/e;
  46. 46. 困ったときは perl のソースを見る • perlop などはあくまで人間向けの説明 • 正しい仕様を確認したいときは perly.y や toke.c 、 keywords.h などを見る • …あまり読めていませんが
  47. 47. 最初の目標 • まずは CPAN 全体のトークナイズに挑戦 • 中期的には Perl::PrereqScanner::NotQuiteLite の 置き換え ( ここまでできれば、 BEGIN の処理などの追 加はそれほど苦労せずにできるはず )
  48. 48. 基本的な構造 while (...) { # 字句解析 if ($code =~ m/G($re_whitespace)/gc) { ...; next; } elsif ($code =~ m/G($re_keyword)/gc) { ... next; } ... elsif ($code =~ m/G($re_anything_else)/gc) { die; } last; } continue { # 構文解析 }
  49. 49. 正規表現を補う道具 my $pos = pos($code); my $c1 = substr($code, $pos, 1); my $c2 = substr($code, $pos, 2); if ($c1 eq '@') { if ($code =~ m/G($re_array)/gc) { ... next; } else { pos($code) = $pos + 1; ... next; } }
  50. 50. 先読みに失敗したら 戻せるように # 内部で先読みしていく if (match_regexp($code)) { ... next; # 正規表現の抽出に成功 } else { # 解釈に失敗したら保存してある位置まで 戻す pos($code) = $pos; }
  51. 51. トークンの収集は 最小限に • 関係ない行のトークンは集めても無駄 • オブジェクトにするのはもっと無駄 • 登録済みのキーワードを見つけたらトークン 収集開始 • メソッドの呼び出し元だけは事前に保存して おく Module->VERSION(1.50) • ブロックのスタックや条件文の存在は別途保
  52. 52. 名前空間や数字は 扱いやすい形に • use などの引数を解釈するときに同じよ うな処理は書きたくない • 一度の正規表現で読み切れるならそれ にこしたことはない • 必要ない字句までまとめる必要はない
  53. 53. テスト • きれいなコードをパースできるのは当 然 • 仕様を片手にあれこれテストコードを 考えるより、何が出てくるかわからな い現実のコードでテストする方が確実 ( 頑張りすぎない )
  54. 54. WorePAN my $worepan = WorePAN->new( use_minicpan => 1, )->walk(sub { my $dir = shift; $dir->recurse(callback => sub { my $file = shift; return unless $file =~ /.pm$/; scan($file); }); }); # 最新版をテストし続けるのには便利
  55. 55. 先に解凍しておく • 毎回解凍するのは時間の無駄 • WorePAN では問題があるファイルを 消せない • もともと壊れているモジュール • Perl6 モジュール
  56. 56. エラーが出たファイ ルだけを詳細に分析 • テスト環境にあわせてデバッグモード を切り替える • 問題を解決できたらテストコードに追 加 • 解析できないモジュールは対象から消 したり、例外に追加したり
  57. 57. 悩まされた コードたち • アポストロフィ • Unicode • 一見キーワード • 無名サブルーチン • プロトタイプ • print $fh • // と / • /#/x • qr)) • eval ''.''
  58. 58. 1. アポストロ フィ • 文字列をくくるだけではありません • Perl 4 時代の名前空間の区切り文字とし てもおなじみ &jcode'convert($value, 'sjis'); • Acme::Don't (Damian Conway さん作 )
  59. 59. 名前空間を一度で …取ろうとしたら m/G(w+(?:(?:'|::)w+)*)/gc
  60. 60. キーワードはあらかじ …め避けておく必要が if (ref($hashOrInternalId) eq'HASH') { PHRED/WebService-NetSuite-0.04/lib/WebService/NetSuite.pm
  61. 61. ヒアドキュメント …と our $SIGNATURE_GRAMMAR = << '#'END'; ... #'END GAAL/Perl6-Signature-0.04/lib/Perl6/Signature.pm
  62. 62. …特殊変数と my $list = shift @'_; my $sep = @$list <= 1 ? '' : do { ... SPROUT/CSS-DOM-0.16/lib/CSS/DOM/PropertyParser.pm
  63. 63. サブルーチン名にまで … sub O'o { [ shift,oO( @_ ) ]->[!$[] } TYEMQ/Acme-ESP-1.002007/ESP.pm
  64. 64. 2. Unicode package Acme::Lambda; use 5.008; use warnings; use strict; use utf8; ... *λ = &lambda; NELHAGE/Acme-Lambda-0.03/lib/Acme/Lambda.pm
  65. 65. 2. Unicode use utf8; package Acme::_; { $Acme::_::VERSION = '0.006'; } BEGIN { $Acme::_::AUTHORITY = 'cpan:ETHER'; } # ABSTRACT: send warnings with _ ETHER/Acme-LookOfDisapproval-0.006/lib/Acme/o_o.pm
  66. 66. 2. Unicode • マルチバイト文字が泣き別れしないよ うに • use utf8; を見つけたら全体を decode • 解析前には BOM のチェックも
  67. 67. 3. 一見キーワー ド sub thx ($) { my ($str) = @_; $INLINE->use if $INLINE; SATOH/Text-Xatena-0.18/t/lib/Text/Xatena/Test.pm
  68. 68. 3. 一見キーワー ドpush @{$data->{stack}}, { in => (caller(1))[3] || '-', package => (caller(1))[0] || '-', sub => (caller(2))[3] || '-', filename => (caller(1))[1] || '-', line => (caller(1))[2] || '-', }; STEVEB/Devel-Trace-Subs-0.22/lib/Devel/Trace/Subs.pm
  69. 69. 3. 一見キーワー ド { } の中に空白がない場合は特別扱い $reg->{ y } = ( $reg->{ y } - 1 ) & 0xff; BRICAS/Games-NES-Emulator-0.03/lib/CPU/Emulator/6502/Op/DEY.pm
  70. 70. 4. 無名サブルーチン enable_if sub { fingerprinted(@_) }, 'Header', set => ['Expires' => MAX_DATE]; INGY/Cog-0.11/lib/Cog/Runner.pm map {} ... なども同じ理由で地雷原
  71. 71. 5. プロトタイプとアトリ ビュートPerl には $) …という特殊変数があるのですが return ( .... $>, # uid $), # gid ... ); MSISK/Net-Nmsg-0.15/lib/Net/Nmsg/Layer.pm
  72. 72. 5. プロトタイプとアトリ ビュート プロトタイプなどは ( ) の組を最優先 sub run_test ($) { AGENT/Makefile-DOM-0.008/t/Shell.pm
  73. 73. 6. ファイルハンドル? 変 数? print $dest_fh <<__EOJS__; ... __EOJS__ DAMI/Alien-GvaScript-1.44/GvaScript_Builder.pm
  74. 74. 6. ファイルハンドル? 変 数? print $dest_fh << 1; ... 1 print $dest_fh << 1; どうやって区別?
  75. 75. 7. // と / srand(((time/$$)^($>*time))/(time/(time^$$))); foreach (1..$length){ $letter = pack("c", rand(128)); redo unless $letter =~ /[a-zA-Z]/; # I just don't like w, okay? $word .= $letter; } JONG/Bioinf_V2.0/Bioinf.pm
  76. 76. 7. // と / 合わせ技 正規表現は変数の直後には来ませんが … print $fh /$var/ ? 1 : 0; print $fh // $var; print $var{foo} / 1; map {...} /$var/ ? 1 : 0;
  77. 77. 8. コメントと正規 表現 m{# trying to cheat with cpants game ;) use strict; use warnings; }x; MONS/AnyEvent-SMTP-0.10/lib/AnyEvent/SMTP/Client.pm
  78. 78. 8. コメントと正規 表現入れ子がおかしくなる場合 $rest =~ s{ b # start at word boundary ( # begin $1 { $urls : # need resource and a colon [$any] +? # followed by on or more ... ) # end $1 } ... }{<A HREF="$1">$1</A>}igox; AUTRIJUS/Pod-HtmlHelp-1.1/WinHtml.pm
  79. 79. 8. コメントと正規表 現次の文字がデリミタになるんじゃなかったの? unless ($$this =~ s # {use (warnings|strict)...)K} {use warnings...)K} {$begin_block}s) { die("Could not add a begin block.n"); } WINTRU/Carrot-1.1.309/lib/Carrot/Modularity/Package/Source_Code.pm
  80. 80. 8. コメントと正規表 現どちらのコメント? $regexp=qr{ .... # (?{ # my $pos=pos; # my $prev=substr($str, $pos-10, 10); # my $post=substr($str, $pos, 10); # print "emitted at position ",pos,...; # }) }xs; OPI/HTML-YaTmpl-1.8/lib/HTML/YaTmpl/_parse.pm
  81. 81. 8. コメントと正規表 現 単一行だけど x の有無で意味が変わる場 合 if ($token =~ m/[ # matches [ /x) { DYLUNIO/Gwybodaeth-0.02/lib/Gwybodaeth/Parsers/N3.pm
  82. 82. 9. …括弧の数が if(my $base_elem = $doc->look_down(_tag => 'base', target => qr)(?:)))){ $name = $base_elem->attr('target'); } SPROUT/WWW-Scripter-0.031/lib/WWW/Scripter.pm
  83. 83. 9. …括弧の数が かっこをひとつ変えるとエラーに Unmatched ) in regex; marked by <-- HERE in m/(?:)) <-- HERE if(my $base_elem = $doc->look_down(_tag => 'base', target => qr((?:)))){
  84. 84. 10. eval: 文字列の中身 は不完全なコードかも $c->{constraint} = eval 'sub { no strict qw/refs/; return defined &{"match_'. $c->{constraint}.'"}(@_)}'; MARKSTOS/Data-FormValidator-4.66/lib/Data/FormValidator/Results.pm
  85. 85. 3 ヶ月ほどの試行錯誤 でトークナイズは ほぼできるように • 例外として登録したモジュール 6 個 • 109 個のテスト • 偶然解析できているものや意味的に間 違っているものが残っている可能性は あり
  86. 86. 古い perl の問題 • なぜか $c1 に 2 文字入ることがあ る ( 初期の 5.8 系列 ) • 取る順序をひっくり返すと直る my $c1 = substr($code, $pos, 1); my $c2 = substr($code, $pos, 2);
  87. 87. 古い perl の問題 巨大で複雑な文字列を正規表現にかける と落ちることがある (5.8 系列すべて ) TOKUHIROM/lib/Amon2/Setup/Asset/Bootstrap.pm
  88. 88. 現状と今後の予定
  89. 89. リポジトリ ( 予定 地 )https://github.com/charsbar/Perl- PrereqScanner-NotQuiteLite
  90. 90. ご清聴ありがと うございました

×