2016/12/20
AST Visitorを使った静的解析
この記事はPowerShell Advent Calendar 2016の20日目です。
はじめに
前々回はASTの概要について述べ、最後にAST.FindAllメソッドを使って、ASTから指定のASTノードを検索する方法について説明しました。
前回はASTを再帰的に検索して、木構造を視覚化してみました。
今回もASTを検索する話なのですが、静的解析機能を実装するためのAST Visitorを用いる方法について説明します。が、あらかじめお断りしておきますが、静的解析の実装までは今回はたどり着きません。静的解析ツールをどう作るかorどう作られているか、ということを雰囲気で味わっていただければと。
Visitorパターン
AST Visitorの説明をする前に、まず、Visitorパターンについて簡単に。
Visitorパターン[Wikipedia]というのは、オブジェクト指向言語におけるデザインパターンの1つで、対象オブジェクトを巡回する「訪問者」クラスを定義するものです。Visitorクラスでは、対象クラスごとに行う処理を、個別にvisitメソッドをオーバーロードさせることで定義します。共通のVisitor抽象クラスを継承することで、異なる機能を持ったVisitorクラスを作ることができます。
一方、処理対象クラスには、Visitorオブジェクトを引数に受け取る、acceptメソッドを定義します。acceptメソッドでは、引数として受け取ったVisitorオブジェクトのvisitメソッドを呼ぶことで、処理を実行させます。
なお、処理対象クラスが子要素クラスを持つ場合には、acceptメソッド内で、子要素クラスのacceptメソッドを呼ぶようにします。こうしておくことで、Visitorは処理対象を再帰的に巡回できるようになります。
このように処理対象クラスから、実際に処理を行う機能をVisitorクラスとして分離することで、処理対象クラスに手を加えることなく、Visitorクラスを追加して、処理内容を増やしたりすることが可能になります。
AST Visitorの呼び出し
Visitorパターンを念頭において、AST Visitorの呼び出し方を見ていきましょう。Ast抽象クラスには、以下の2つのVisitメソッドが定義されています。
説明に入る前に注意点。メソッド名は"Visit"となっていますが、Visitorパターンでいうところの"accept"メソッドのことです。なぜメソッド名がAcceptじゃないのかは不明ですが…。
ともかく、AstクラスのVisitメソッドは、AstVisitor抽象クラスを継承したクラスのオブジェクトか、ICustomAstVisitorインターフェースを実装したクラスのいずれかを引数に取ることで、ASTに対する処理を実施します。
AstVisitor抽象クラスを継承、もしくはICustomAstVisitorインターフェースを実装することで、ASTの種類に応じた巡回処理を行うクラスを、自分で定義していきます。
AstVisitor抽象クラス
AstVisitor抽象クラスは、Visitorとしての基本的な機能があらかじめ実装されています。具体的には既に以下の機能は用意されています。
- ASTの種類に応じたVisitメソッドの定義
すべての種類のASTに対応するVisitメソッド(50個以上)がVirtualメソッドとして定義されています(※)。例えば、IfStatementAstに対する処理を行うための、VisitIfStatementメソッドがあります。※一般的なVisitorパターンでは、Visitメソッドを対象クラス分オーバーロードさせますが、PowerShellのAstVisitorは対象クラスに応じた別名のメソッドを定義する方式です。これも理由は分かりませんが、オーバーロードにするには多すぎるからかもしれません。
- 子ノードの再帰的な巡回
各Visitメソッドには、ASTの子ノードに対し、再帰的にVisitメソッドを呼ぶ仕掛けがあらかじめ備わっています。 - ノード巡回の停止
各VisitメソッドはAstVisitAction列挙型を返却します。以下のように返却する値によって、ノード巡回の継続、停止を制御できます。- Continue:ノード巡回を継続(デフォルト)
- SkipChildren:子ノードの巡回を行わない
- StopVisit:巡回を終了する
カスタムAstVisitorクラスを作成する
以上の基本的な機能を踏まえて、AstVisitor抽象クラスを実装したカスタムVisitorクラスを作ります。C#で書くのが一般的ですが、せっかくなのでPowerShell v5で追加された、クラス構文を使って書いてみましょう。
例えば、「利用しているコマンドのリストを取得する。ただし、コマンドのパラメータ内で別コマンドを呼び出している場合は除く。」というお題を解くことを考えます。
ASTのFindAllメソッドだと、配下に含まれるすべてのCommandAstを取得してしまうので、単純にはいきません。そこでカスタムAstVisitorクラスの出番です。
このお題を実現するVisitorクラスは以下のようになるでしょう。
using namespace System.Management.Automation.Language class GetCommandNamesVisitor : AstVisitor { [string[]]$CommandNames = @() [AstVisitAction]VisitCommand([CommandAst]$commandAst) { $this.CommandNames += $commandAst.CommandElements[0].Extent.Text return [AstVisitAction]::SkipChildren } }
PowerShellのクラス構文において、Virtualメソッドのオーバーライドは、単に同名のメソッドを定義するだけですので、ここではVisitCommandメソッドをオーバーライドします。
プロパティとフィールドの区別はないので、コマンド名の一覧を格納するCommandNamesプロパティは上記のような定義になります。メソッド内でクラスメンバを参照する際には$thisを用います。
作成したGetCommandNamesVisitorクラスをインスタンス化し、解析対象スクリプトブロックのASTのVisitメソッドに引数として渡します。
$scriptBlock = { $files = Get-ChildItem -Path (Get-Location | Split-Path -Parent) -File $files | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 5 } $visitor = New-Object GetCommandNamesVisitor $scriptBlock.Ast.Visit($visitor) $visitor.CommandNames
実行すると、結果は
Get-ChildItem Sort-Object Select-Object
のようになるかと思います。
AstVisitorクラスの具体的な実装については、PSReadLineやPowerShellEditorServicesにありますので、参考にしてみてください。
ICustomAstVisitorの実装
前項で述べた、AstVisitor抽象クラスを継承したカスタムAstVisitorクラスの場合、基本的な処理を実装する必要はないですし、目的とするASTクラスに対するVisitメソッドだけオーバーライドすればいいので、非常に簡便です。
ただ、本格的にPowerShellの構文解析を行いたい場合、ノードの巡回順だとか、その他もろもろをもっと細かく自分で実装したいケースが出てきます。
そういった場合にはICustomAstVisitorインターフェースを実装したクラスを作って対応します。ICustomAstVisitorインターフェースも、AstVisitor抽象クラス同様、各ASTクラスに応じたVisitメソッドが定義されているのですが、各VisitメソッドはAstVisitAction列挙体ではなく、object型のオブジェクトを返します。つまり、自分で好きなオブジェクトを返すように定義できるわけです。
Ast.Visit(ICustomAstVisitor)はAstVisitor抽象クラスを引数に取る場合と異なり、objectを返却するのですが、このとき返却されるのは、最初に実行されたVisitメソッドの戻り値になります。
ICustomAstVisitorはインターフェースですので、処理はすべて自分で定義しなくてはなりません(※)。ノードの再帰的探索も、必要ならもちろん自前で実装する必要があります(前回紹介した、JSON化スクリプトのような処理になるかと思います)。
※ISEだとインターフェースの実装を一発で行うリファクタリング機能はないので、今回みたく実装すべきメンバがたくさんある場合は、こんな感じのひな形を作るスクリプトを使うと良いでしょう。
今回はICustomAstVisitorインターフェースを実装したクラスの実例まではご紹介できませんでしたが、興味のある方は、PSScriptAnalyzerで用いられているので参考にしてみてください。
まとめ
PowerShellのASTについてきちんと解説している記事が英語圏を含めてもあまりないようでしたので、3回に渡って、一通りの基礎知識をまとめてみました。
普通にPowerShellを使っている分には、滅多に使うことはないと思いますが、たとえばPSScriptAnalyzerのカスタムルールを自分で作る場合には、ASTの知識は必須になってきますので、必要に応じて参考にしていただければ幸いです。
2015/06/14
わんくま同盟大阪勉強会#63「PowerShell DSCリソースを書いてみよう」資料公開
わんくま同盟大阪勉強会#63でのセッション資料を公開します。
デモで使用したサンプルスクリプトも併せてご利用ください。
わんくま同盟の勉強会は今回の大阪#63で10年目に入ったのですが、実は私も9年前の大阪#4で勉強会セッションデビューをしていたりします。
さて、今回はPowerShell DSCリソースを作成するというテーマで話しました。DSCでは管理対象ごとにロジック(リソース)と設定(Configuration)を分離でき、一貫性を持ったインフラ構成の自動化が可能になる素敵な機能です。が、いかんせん、ビルトインリソース(OS標準で含まれるリソース)の種類が少ないので、実際の業務で使うにはカスタムリソースを作成する必要が出てくると思います。
今回のデモでは、テキストファイルの中身を自動構成するという、ごく単純なサンプルを作成して実演してみましたが、考え方や実装方法の基本はこれでカバーできるのではないかと思います。
より詳しくは、ぎたぱそ氏の記事を読んでいただければ良いかと思います。本番で使えるPowerShell DSCリソース作成入門 - Build Insider
サンプルスクリプトの説明
-
事前準備
今回のデモはWin10 Insider Preview(PowerShell 5.0)上で行いましたが、PowerShell 4.0環境でもおそらく動作すると思います。なお、今回のConfigurationはローカルコンピュータに対しPush適用(Start-DscConfigurationコマンドレットによる手動適用)することを想定しています。あらかじめ、DSCが実行可能な環境(スクリプト実行ポリシー、PSリモーティング、Local Configuration Managerの設定等)を整えておいてください。また、スクリプトはすべて管理者権限で実行してください。
-
xDSCResourceDesignerのインストール
DSCリソースのひな形を作成するためのxDSCResourceDesignerをインストールします。v5環境であればPowerShellGetを用い、Install-Module xDSCResourceDesigner で入ります。v4環境の場合はTechnetからDSC Resource KitをDLしてください。
-
xDSCResourceDesignerの使用方法の確認
xDSCResourceDesignerの使用方法を確認します。make_Foo_resource_template.ps1を実行すると、xDSCResourceDesignerを用いてDSCリソースFooのひな形が$env:ProgramFiles\WindowsPowerShell\Modules\TestResourceに作成されます。ひな形がどのように作成されるかを確認してください。また、Get-DscResource -Name Fooとして、DSCリソースがきちんと認識されているか、確認してください。
-
TextFileLineリソースの作成
今回のデモで作成したTextFileLineリソースは、make_TextFileLine_resource_template.ps1を実行してまずひな形を作成しました。作成されたひな形を用いて、TextFileLine.psm1に実際のロジックを記述し、DSCリソースを完成させました。zipに含まれるTestResourceフォルダの中身が、今回作成したDSCリソースモジュールになりますので、まずは内容を確認してみてください。特に、Set-TargetResource関数はどのように実装すると冪等性を保持して作成できるのかを念頭に置いてみてください。
-
TextFileLineリソースの展開
zipに含まれるTestResourceフォルダを$env:ProgramFiles\WindowsPowerShell\Modules\の下にコピーしてください。
Get-DscResource -Name TextFileLineとしてDSCリソースが認識されていることを確認してください。
-
TextFileLineリソースを用いたConfigurationの作成
start_dsc_configuration.ps1に含まれる、TextFileLineTestが、今回適用してみるConfigurationになります。
start_dsc_configuration.ps1を実行すると、ドキュメントフォルダにmofファイルを生成し、Start-DscConfigurationコマンドレットにより設定を反映させます。
正しくConfigurationが適用されれば、ドキュメントフォルダにlist.txtというファイルが生成し、中にプログラム言語のリストが記入されているはずです。
-
Configurationが反映されたことを確認
Test-DscConfigurationコマンドレットを実行すると、現在の状態とConfigurationに書かれた状態が一致していればTrueと表示されます。今はConfigurationを適用した直後なのでTrueになるはずです。
またGet-DscConfigurationコマンドレットを実行すると、現在の各プロパティの状態を表示してくれます。
list.txtに含まれる行をテキストエディタで編集して上書きしたりすると、Test-DscConfigurationの結果はFalseになるはずです。
list.txtを手動で変更した状態で、再度start_dsc_configuration.ps1を実行すると、再びConfiguration通りの状態に戻ると思います。その際、変更のなかったプロパティに関しては、処理がスキップされていることをログをみて確認してください。
-
その他
Configurationを色々書き換えて試してください。例えばEnsure="Absent"にすると対象項目が存在しない状態になります。
2007/05/24
[Gadget]実用的なフィードヘッドラインを作る(2)
基本は赤坂さんのCodezineの記事を追っていくことにしましょう。
まずガジェットのひな形を作るには
1.ガジェット保存フォルダ(C:\Users\<ユーザー名>\AppData\Local\Microsoft\Windows Sidebar\Gadgets\)に
2.作るガジェットのサブフォルダ(<ガジェット名.gadget>)を作り、
3.その中にgadget.xmlファイルを作る
4.また、ガジェットの実体であるhtmlファイルを作る
これで、ガジェットの追加ダイアログに表示されるようになります。
例は赤坂さんの記事にあるので、ここでは実際のコードを。
■C:\Users\daisuke\AppData\Local\Microsoft\Windows Sidebar\Gadgets\Large_Feed.gadget\gadget.xml
フィード ヘッドライン(大) 1.0.0.0 Full
■C:\Users\daisuke\AppData\Local\Microsoft\Windows Sidebar\Gadgets\Large_Feed.gadget\large_feed.html
フィード ヘッドライン(大) ここにフィードが表示されます。
さて、これを実際に表示させるとこのようになります。
まだ何も機能しませんが、立派にガジェットができました。
gadget.xmlの文法などは赤坂さんの説明をご参照ください。
ちなみにドラッグしてデスクトップに置く(アンドックする)とサイズが変わります。これは、System.Gadget.onDockイベントにサイズ変更のための関数(Function)の関数ポインタを代入していることで実現しています。VBScriptではGetRef関数が関数ポインタを扱う関数です。関数ポインタってなに?ってことはあまり気にしないで、とにかくイベントと実際の動作を関連付けるとだけ理解してればいいでしょう。
今回はここまでです。次回はいよいよRSS取得に挑戦してみます?予定は未定w
元記事:http://blogs.wankuma.com/mutaguchi/archive/2007/05/24/78230.aspx
Copyright © 2005-2018 Daisuke Mutaguchi All rights reserved
mailto: mutaguchi at roy.hi-ho.ne.jp
プライバシーポリシー