MPS 2019.3ヘルプ

データ・フロー・エンジンとの統合方法

データフロー

データフロー分析では、簡単には見つけられないエラーの検出をサポートしています。
静的制約または型チェックによる「モデルの検討」例としては
デッドコードの検出、メソッド本体のブランチでの戻り値の欠落
null 値を静的に検索し、NULLポインタ例外を防止します。

データフロー分析の基盤は、いわゆるデータフローグラフです。これは
プログラムのコードを通るデータの流れを記述するデータ構造。にとって
たとえば、int i = 42; j = i + 1; では、42はinitから「流れる」
ローカル変数宣言内の式を変数 i にしてから、
jに1を加えた後。データフロー分析は、2つのタスクで構成されています。
プログラムのデータフローグラフ、このデータフローの分析の実行
プログラム内の問題を検出するためのグラフ。

MPSはデータフローグラフのための事前定義されたデータ構造、DSLのために来ます
グラフを言語の概念からどのように導出できるかを定義する(したがって、
プログラム)、グラフ上で独自の分析を定義するためのフレームワーク、および
あなたの言語に統合することができるデフォルト分析のセット。見てみよう
このセクションのすべてのこれらの原料。

データフローグラフを操作するには、Javaプログラムでメソッドを選択し、
次に、メソッドのコンテキストメニューを使用します。{{Language Debug-> Show Dataを選択
Flowグラフ}}。これにより、データフローグラフがグラフィカルにレンダリングされ、
独自のデータフローグラフと分析を作成する際の優れたデバッグツール。

データFlowグラフの作成

単純な線形データフロー

このセクションでは、次のコードを調べます。
C言語に似た言語のデータフローグラフを作成するためには、
Java(実際、mbeddr.com Cベース言語用です)。

データフローは言語定義のデータフローアスペクトで指定されています。
その側面の中で、あなたの言語のためにデータフロービルダー(DFB)を追加することができます
コンセプトこれらのインスタンスのデータフローグラフを作成するプログラムです。
プログラムにおけるそれらの概念。はじめに、これがDFBです。
LocalVariableDeclaration

data flow builder for LocalVariableDeclaration { (node)->void { if (node.init != null) { code for node.init write node = node.init } else { nop } } }

これを詳しく調べてみましょう。フレームワークは、このDFBが定義されている概念の現在のインスタンス(ここではLocalVariableDecaration )を参照する方法として、node 変数を渡します。
LocalVariableDecaration にinit式がある場合(これはオプションです)、init式の
DFBを実行する必要があります。 code for ステートメントは
これを行います。引数として渡されたノードのDFBを「呼び出し」ます。次に、実際のデータフロー定義を実行します。write node = node.init は、
現在のノードで書き込みアクセスが実行されることを指定します(
read ステートメントもあります。これは、書き込み前読み取りエラーの検出をサポートしています)。
ステートメントは、init式に含まれていた値がノード自体にも含まれていることを表します。

init 式がない場合でも、マークを付けます。
訪問した LocalVariableDeclaration ノード - プログラムフローが来ました
このノード全体。その後の分析では、すべてのプログラムノードが
DFBからデッドコードとして訪問されたことはありません。ノードがそれ以上ない場合でも
プログラムのデータフローに影響を与える場合は、nopを使用して訪問済みとしてマークする必要があります。

read ステートメントを説明するために、以下を参照してください。
変数itにリードアクセスする LocalVariableRef
参照そのデータフローは read node.var}, where {{var}として定義されます。
参照された変数を指す参照の名前これが
コード:

data flow builder for LocalVarRef { (node)->void { read node.var } }

AssignmentStatementの場合、データフローは次のとおりです。

data flow builder for AssigmentStatement { (node)->void { code for node.rvalue write node.lvalue = node.rvalue } }

rvalue に対して最初にDFBを実行し、次にそれを「フロー」する方法に注意してください。
rvalue から lvalue へ - 割り当ての目的

StatementListの場合は、リストを訪問済みとしてマークしてから実行します。
各ステートメントのDFB

nop foreach statement in node.statements { code for statement }

最後に、C関数では、少なくとも今のところ、引数を無視して、DFBは単純に
ボディのDFB、StatementListを呼び出します。

これで、単純な関数についてデータフローグラフを調べる準備が整いました。
以下は関数のグラフです。

dfgexample

データフロー分析は通常、1つの機能、方法などに限定されます。
概念。それらのうちの1つの終わりを知らせるために、ret を使うべきです
ステートメント。これを説明するために、ReturnStatementのDFBを次に示します。

if (node.expression != null) { code for node.expression } ret

ブランチ

前述のように、線形データフローは比較的
まっすぐ進む(意図された駄目はない)。ただし、最も興味深いデータフロー
分析はループと分岐に関係しています。そのために正しいDFBを指定する
if , switchfor のようなものは重要です。それもそうではありません
単純な\ ldots

IfStatementのDFBを段階的に見てみましょう。まず、必須の nop を使用して、訪問したノードを作成します。次に、条件のDFBを実行します。これは、いずれの場合も評価されるためです。その後、興味深いことに、条件が真か偽かによって、thenPart} or we jump to where the {{else if 部分の実行が始まります。これまでのコードは次のとおりです。

nop code for node.condition ifjump after elseIfBlock // elseIfBlock is a label defined later code for node.thenPart { jump after node }

ifjump ステートメントは、指定されたラベルにジャンプできることを意味します(たとえば
それから{{else if}}を実行します。もしそうでなければ
ifjmp}), then we execute the {{thenPart実行すると
thenPart}, we are finished with the whole {{IfStatement - いいえ
else if}s or {{else 部品が関連しているため、現在の後にジャンプします
ノード( IfStatement)で完了です。しかし、追加のものがあります
catch: thenPart}, there may be a {{return ステートメント内。
実際に jump after node ステートメントにたどり着かないでください。これが理由です
波括弧で囲まれています。これは、中括弧内のコードがオプションであることを示しています。もし
データフローはそれを訪問しません。それは結構です(通常私達がから戻るため)
このコードを実行する機会を得る前のメソッド)

else if}s. We arrive at the {{elseIfBlock ラベルを続けましょう
条件が偽であれば、すなわち上記の ifjump が実際に起こったのです。我々
それから{{elseIf}}を反復処理してそれらのDFBを実行します。その後、走ります
elsePartのコード(ある場合)次のコードは
それを知っていれば、{{else if}}のいずれかを実行すれば、
IfStatement 全体を飛び越えます。これは、DFBに指定されています。
以下に説明する ElseIfPartこれが残りのコードです。
IfStatementのDFBの場合:

label elseIfBlock foreach elseIf in node.elseIfs { code for elseIf } if (node.elsePart != null) { code for node.elsePart }

これで ElseIfPartのDFBをインスペクションできます。まずDFBを実行します。
その状態それから、その else ifの後にジャンプするかもしれません。
条件が偽である可能性があるため、次の else ifを試してみます。
1。あるいは、条件が真であれば、次に体に対してDFBを実行します。
ElseIfPartのそれから2つのことが起こり得ます。
あなたの全体 IfStatement} (after all, we have found an {{else if です
true)、または現在の else if が原因でもう何もしません。
returnステートメントを含みます。再び波括弧を使用する必要があります
if全体の後にジャンプします。これがコードです:

code for node.condition ifjump after node code for node.body { jump after node.ancestor<concept = IfStatement> }

得られたデータフローグラフを以下に示します。

ifdfg

ループ

データフローグラフの作成をまとめると、次のようになります。
for ループを参照してください。これはまた分岐に関連しています。
すべて、ループは分岐とジャンプにリファクタリングできます。これがDFBです。
for ループ:

code for node.iterator label start code for node.condition ifjump after node code for node.body code for node.incr jump after start

最初に iterator のDFBを実行します(これは一種の
LocalVariableDeclaration、それでそれのためのDFBは働きます)。それでは、
start とラベルを付けると、さらに下からこの場所にジャンプできます。それから
ループ全体の後に condition}. Then we have an {{ifjmp を実行する
(これは条件が偽でループが終了する場合をカバーしています)。の中に
それ以外の場合(条件はまだ当てはまります)、body のコードを実行します。
そして incr} part of the {{for ループ
上で定義した start ラベル

データFlowチェックをあなたの言語に統合する

データフローチェックは NonTypesystemRulesからトリガされます。少しあります
手続き型コードを書く必要があるため、クラスを作成します
型システムアスペクトモデルの DataflowUtil

public class DataflowUtil extends <none> implements <none> { private Program prog; public DataflowUtil(node<> root) { // build a program object and store it prog = DataFlow.buildProgram(root); } @CheckingMethod public void checkForUnreachableNodes() { // grab all instructions that are unreachable (predefined functionality) sequence<Instruction> allUnreachableInstructions = ((sequence<Instruction>) prog.getUnreachableInstructions()); // remove those that may legally be unreachable sequence<Instruction> allWithoutMayBeUnreachable = allUnreachableInstructions.where({~instruction => !(Boolean.TRUE.equals(instruction. getUserObject("mayBeUnreachable"))); }); // get the program nodes that correspond to the unreachable instructions sequence<node<>> unreachableNodes = allWithoutMayBeUnreachable. select({~instruction => ((node<>) instruction.getSource()); }); // output errors for each of those unreachable nodes foreach unreachableNode in unreachableNodes { error "unreachable code" -> unreachableNode; } } }

クラスはコンストラクター内で{{Program}オブジェクトを作成します。{{Program}}はデータフローグラフのラッパーであり、グラフへのアクセスやグラフ上の一連の定義済み分析へのアクセスを提供します。ここでは checkForUnreachableNodes メソッドでそれらのうちの1つを使用します。このメソッドはグラフからすべての到達不能ノードを抽出し(上のコードのコメントを参照)、それらに対するエラーを報告します。 error ステートメントを使用できるようにするには、@CheckingMethod 注釈でメソッドに
注釈を付ける必要があります。

実際にチェックを実行するには、NonTypesystemRule からこのメソッドを呼び出します。
C関数の場合

checking rule check_DataFlow { applicable for concept = Function as fct overrides false do { new DataflowUtil(fct.body).checkForUnreachableNodes(); } }

Program クラスを調べると、既存の他のデータのセットを見ることができます
フロー解析:初期化されていない読み取り(書き込み前に読み取り)、到達不可能な命令
(デッドコード)と未使用の割り当て。そうでない場合の対処方法を見ていきます
次のセクションで十分です。

独自のアナライザを構築する

データフロー分析は重要なトピックです。意味のある分析を構築するには
おそらくこのトピックの背景が必要か、またはそれぞれのトピックを参照してください。
文献。

カスタムデータフロー分析を実際に統合するために、
後で追加の記事。

最終更新日: 2020年2月4日