データフローエンジンとの統合方法
データフロー
データフロー分析は、静的制約またはタイプチェックを使用して「モデルを見る」ことでは簡単に見つけることができないエラーの検出をサポートします。例としては、デッドコードの検出、メソッド本体の一部のブランチでの戻り値の欠落、null
値の静的な検索、null ポインター例外の防止などがあります。
データフロー分析の基盤は、いわゆるデータフローグラフです。これは、プログラムのコードを介したデータの流れを記述するデータ構造です。例: int i = 42; j = i + 1;
では、42 はローカル変数宣言の init 式から変数 i
に「流れ」、1 を追加した後 j に流れます。データフロー分析は、プログラムのデータフローグラフを作成することと、このデータフローグラフで分析を実行してプログラムの問題を検出することの 2 つのタスクで構成されます。
MPS には、データフローグラフ用の事前定義されたデータ構造、言語の概念(したがってプログラム)からグラフを導出する方法を定義するための DSL、グラフ上で独自の分析を定義するためのフレームワーク、およびあなたの言語に統合することができます。このセクションでは、これらすべての成分を見ていきます。
データフローグラフを操作するには、Java プログラムでメソッドを選択してから、そのメソッドのコンテキストメニューを使用します。{{Language Debug-> Show Data Flow Graph}} を選択します。これにより、データフローグラフがグラフィカルにレンダリングされ、独自のデータフローグラフや分析を作成する際の優れたデバッグツールが構成されます。
データ Flow グラフの作成
単純な線形データフロー
このセクションでは、C および Java に類似した言語(実際には、mbeddr.com C 基本言語用)のデータフローグラフを作成するために作成する必要のあるコードについて説明します。
データフローは、言語定義のデータフローアスペクトで指定されます。その側面の中で、言語の概念に合わせてデータフロービルダー(DFB)を追加できます。これらは、プログラム内のこれらの概念のインスタンスのデータフローグラフを作成するプログラムです。はじめに、LocalVariableDeclaration
の DFB を次に示します。
これを詳しく調べてみましょう。
フレームワークは、この DFB が定義されているコンセプトの現在のインスタンス (ここでは LocalVariableDecaration
) を参照する方法として、node
変数を渡します。LocalVariableDecaration
に init 式がある場合 (オプションです)、init 式の DFB を実行する必要があります。code for
ステートメントはこれを実行します。引数として渡されたノードの DFB を「呼び出し」ます。次に、実際のデータフロー定義を実行します。write node = node.init
は、現在のノードで書き込みアクセスが実行されることを指定します (read
ステートメントもあり、これは書き込み前の読み取りエラーの検出をサポートします)。このステートメントは、init 式にあった値がノード自体にあることも表します。
init
式がない場合でも、LocalVariableDeclaration
ノードを訪問済みとしてマークする必要があります。プログラムフローはこのノードに到達しています。その後の分析では、DFB がアクセスしていないすべてのプログラムノードがデッドコードとして報告されます。ノードがプログラムのデータフローにそれ以上影響を与えない場合でも、nop
を使用して訪問済みとしてマークする必要があります。
read
ステートメントを説明するために、参照する変数を読み取りアクセスする LocalVariableRef
式を見てみましょう。そのデータフローは次のように定義されます。read node.var}, where {{var
} は、参照される変数を指す参照の名前です。
コードは次のとおりです。
AssignmentStatement
の場合、データフローは次のとおりです。
最初に rvalue
の DFB を実行し、次に rvalue
を lvalue
に「フロー」する方法に注意してください — 割り当ての目的です。
StatementList
の場合、リストに訪問済みのマークを付けてから、各ステートメントの DFB を実行します。
最後に、C 関数の場合、少なくとも今のところ、引数を無視して、DFB は本体の DFB である StatementList
を呼び出すだけです。
これで、データフローグラフを調べて簡単な関数を探す準備ができました。以下は関数のグラフです。

データフロー分析は通常、1 つの機能、方法、同様の概念に限定されます。これらのいずれかの終了を通知するには、ret
ステートメントを使用する必要があります。これを説明するために、ReturnStatement
の DFB を次に示します。
ブランチ
上記のように、線形データフローは比較的単純です(しゃれは意図されていません)。ただし、最も興味深いデータフロー分析は、ループと分岐に関係しています。if
、switch
、for
などの正しい DFB を指定することが重要です。また、それほど単純ではありません \ ldots。
IfStatement
の DFB を段階的に見てみましょう。
まず、ノードを訪問済みにするための必須の nop
から始めます。次に、条件に対して DFB を実行します。これは、どのような場合でも評価されるためです。次に、興味深いことになります。条件が真であるか偽であるかに応じて、thenPart} or we jump to where the {{else if
パーツの開始を実行します。これまでのコードは次のとおりです。
ifjump
ステートメントは、指定されたラベルに移動できることを意味します(つまり、{{else if}} を実行します)。そうでない場合(ifjmp}), then we execute the {{thenPart
を「オーバーオーバー」するだけです。thenPart}, we are finished with the whole {{IfStatement
を実行する場合 — else if}s or {{else
パーツは関連しないため、現在のノード(IfStatement
)の後に移動して完了します。ただし、追加のキャッチがあります。thenPart}, there may be a {{return
ステートメント。実際に jump after node
ステートメントに到達することはないかもしれません。これが波括弧で囲まれている理由です。これは、中括弧内のコードがオプションであることを示しています。データフローがアクセスしない場合は、問題ありません(通常はこのコードを実行する機会を得る前に、メソッドから戻ります)。
条件が false の場合、つまり上記の ifjump
が実際に発生した場合は、else if}s. We arrive at the {{elseIfBlock
ラベルを続行します。次に、{{elseIf}} を繰り返し処理し、DFB を実行します。その後、elsePart
のコードがあれば、それを実行します。次のコードは、{{else if}} の 1 つを実行すると、 IfStatement
全体の後に移動することがわかっている場合にのみ理解できます。これは、ElseIfPart
の DFB で指定されています。これについては、以下で説明します。IfStatement
の DFB の残りのコードは次のとおりです。
これで、DFB の ElseIfPart
をインスペクションできます。まず、条件に対して DFB を実行します。次に、条件が false である可能性があり、次の else if
がある場合はそれを試したいため、その else if
の後に移動します。または、条件が true の場合、ElseIfPart
の本体に対して DFB を実行します。次に、2 つのことが起こります。IfStatement} (after all, we have found an {{else if
全体が真の後に移動するか、現在の else if
に return ステートメントが含まれているために何も実行しなくなります。if
全体の後に移動するには、波括弧を再度使用する必要があります。コードは次のとおりです。
得られたデータフローグラフを以下に示します。

ループ
データフローグラフの構築をまとめるために、for
ループを見てみましょう。結局のところ、ループは分岐と移動にリファクタリングできるため、これは再び分岐に関連しています。for
ループの DFB は次のとおりです。
最初に iterator
の DFB を実行します(これは LocalVariableDeclaration
の一種であるため、上記の DFB は機能します)。次に、ラベル start
を定義して、さらに下からこの場所に移動できるようにします。次に、ループ全体の後に condition}. Then we have an {{ifjmp
を実行します(これは、条件が false でループが終了する場合をカバーします)。それ以外の場合(条件はまだ真です)、body
および incr} part of the {{for
ループのコードを実行します。次に、上記で定義した start
ラベルの後に移動します。
データ Flow チェックをあなたの言語に統合する
データフローチェックは NonTypesystemRules
からトリガーされます。記述する必要のある手続き型コードが少しあるため、型システムアスペクトモデルでクラス DataflowUtil
を作成します。
このクラスは、コンストラクターで {{Program} オブジェクトを作成します。{{Program}} は、データフローグラフのラッパーであり、グラフへのアクセスと、グラフ上の事前定義された分析のセットへのアクセスを提供します。ここでは、そのうちの 1 つを checkForUnreachableNodes
メソッドで使用します。このメソッドは、グラフから到達不能なノードをすべて抽出し(上記のコードのコメントを参照)、それらのエラーを報告します。error
ステートメントを使用できるようにするには、メソッドに @CheckingMethod
アノテーションを付ける必要があります。
実際にチェックを実行するには、C 関数の NonTypesystemRule
からこのメソッドを呼び出します。
Program
クラスを調べると、既存の他のデータフロー分析のセットを確認できます。初期化されていない読み取り(書き込み前の読み取り)、到達不能な命令(デッドコード)、未使用の割り当てです。次のセクションでは、それらが十分でない場合の対処方法を見ていきます。
独自のアナライザーを構築する
データフロー分析は重要なトピックです。意味のある分析を構築するには、おそらくこのトピックの背景が必要になるか、それぞれの文献を読む必要があります。
カスタムデータフロー分析の実際の統合については、後で追加の記事を提供します。
関連ページ:

データフロー
このクックブックでは、あなたの言語用のデータフローを設計する際の素早い答えとガイドラインを提供するべきです。型システムの詳細な説明については、ユーザーガイドのデータフローセクションを参照してください。値を読む:読み取り操作は、特定の値が読み取られることをデータフローエンジンに指示します。data flow builder for LocalVariableReference { (node)->void { if (node.isVariableDefinedInThisMethod()

ジェネレータークックブック
この文書は、MPS ジェネレーターに関する最も一般的な質問に対する回答を提供することを目的としています。代わりにジェネレーターのドキュメントを調べてジェネレーターデモをチェックすることもできます。ジェネレーターはどのようにルールを処理しますか? :生成は、入力モデルを徐々に出力モデルに変換します。出力モデルは、TextGen を使用してテキストに変換する場合としない場合があります。生成プロセス自体はステップで構成されています。各ステップには 3 つのフェーズがあります。マッピング前スクリプトの実...