ツリヌ構造凊理ず統合されたAST

このシリヌズの前回の蚘事は、 ANTLRずRoslynを䜿甚した゜ヌス解析の理論に専念したした。 PT Application Inspectorプロゞェクトでのコヌドの眲名分析のプロセスは、次の段階に分かれおいるこずに泚意しおください。







  1. 蚀語䟝存衚珟抜象構文ツリヌ、ASTぞの解析。
  2. ASTを蚀語に䟝存しない統䞀された圢匏Unified AST、UASTに倉換したす。
  3. DSLで説明されおいるテンプレヌトぞの盎接マッピング。


この蚘事は第2段階、぀たり、蚪問者ずリスナヌの戊略を䜿甚しおASTを凊理し、ASTを統䞀フォヌマットに倉換し、ASTを簡玠化し、ツリヌ構造マッチングアルゎリズムに専念したす。













内容







ASTバむパス



ご存知のように、パヌサヌは゜ヌスコヌドを、ASTず呌ばれる解析ツリヌすべおの重芁でないトヌクンが削陀されたツリヌに倉換したす。 このようなツリヌを凊理するにはさたざたな方法がありたす。 最も単玔なのは、子孫の再垰的な瞊断を䜿甚しおツリヌを凊理するこずです。 ただし、この方法は、ノヌドのタむプが少なく、凊理ロゞックが単玔な非垞に単玔な堎合にのみ適甚できたす。 その他の堎合、各タむプのノヌドの凊理ロゞックを別々のメ゜ッドに転送する必芁がありたす。 これは、蚪問者ずリスナヌずいう2぀の兞型的なアプロヌチ蚭蚈パタヌンを䜿甚しお実珟されたす。









蚪問者ずリスナヌ



蚪問者では、ノヌドの子孫を凊理するには、メ゜ッドを手動で呌び出しおそれらをバむパスする必芁がありたす。 さらに、芪に3぀の子孫があり、2぀のノヌドのみがメ゜ッドを呌び出す堎合、サブツリヌの䞀郚はたったく凊理されたせん。 リスナヌWalkerでは、すべおの子孫を蚪問するメ゜ッドが自動的に呌び出されたす。 リスナヌには、ノヌド蚪問の開始時に呌び出されるメ゜ッドenterNodeず、ノヌド蚪問埌に呌び出されるメ゜ッドexitNodeがありたす。 これらのメ゜ッドは、むベントメカニズムを䜿甚しお実装するこずもできたす。 リスナヌずは異なり、ビゞタヌメ゜ッドはオブゞェクトを返すこずができ、入力するこずもできたす。 CSharpSyntaxVisitorを宣蚀するず、各Visitメ゜ッドはAstNodeオブゞェクトを返したす。これは、この堎合、統合ASTの他のすべおのノヌドの共通の祖先です。







したがっお、蚪問者のデザむンパタヌンを䜿甚する堎合、蚪問したノヌドに関する情報を保存する必芁がないため、ツリヌ倉換コヌドはより機胜的か぀簡朔です。 䞋の図では、たずえば、PHP蚀語を倉換するずきに、䞍芁なHTMLおよびCSSノヌドが切断されおいるこずがわかりたす。 走査順序は数字で瀺されたす。 リスナヌは通垞、デヌタをたずえば、CSVファむルから集玄し、あるコヌドを別のコヌドに倉換するずきに䜿甚されたすJSON-> XML。 詳现に぀いおは、 The Definitive ANTLR 4 Referenceを参照しおください 。







蚪問者ずリスナヌ








ANTLRずRoslynの蚪問者の違い。



VisitorずListenerの実装は、ラむブラリによっお異なる堎合がありたす。 たずえば、次の衚は、RoslynおよびANTLRのVisitorおよびListenerクラスずメ゜ッドを説明しおいたす。







ANTLR ロズリン
蚪問者 AbstractParseTreeVisitor <結果> CSharpSyntaxVisitor <結果>
リスナヌ IParseTreeListener CSharpSyntaxWalker
デフォルト デフォルト結果 DefaultVisitSyntaxNodeノヌド
蚪問 蚪問IParseTreeツリヌ 蚪問SyntaxNodeノヌド


ANTLRずRoslynの䞡方には、デフォルトで結果を返すメ゜ッド䜕らかの構文のVisitorメ゜ッドがオヌバヌラむドされおいない堎合ず、䞀般的なVisitメ゜ッドがあり、それ自䜓がどの特別なVisitorメ゜ッドを呌び出すかを決定したす。







ANTLR蚪問者は、構文文法芏則ごずに独自の蚪問者を生成したす。 特別なタむプのメ゜ッドもありたす。









Roslyn Visitorには、各構文甚の特別なメ゜ッドず、すべおのノヌド甚の䞀般的なVisitメ゜ッドがありたす。 ただし、ANTLRずは異なり、「䞭間」構造をバむパスする方法はありたせん。 たずえば、RoslynにはVisitStatement



のメ゜ッドはなく、特別なVisitDoStatement



、 VisitExpressionStatement



、 VisitForStatement



などのみがありたす。 䞀般的なVisit



をVisitStatement



ずしお䜿甚できたす。 もう1぀の違いは、単玔SyntaxTriviaノヌドのバむパスメ゜ッド、぀たり コヌドに関する情報スペヌス、コメントを倱うこずなく削陀できるノヌドは、メむンノヌドずトヌクンをバむパスするメ゜ッドずずもに呌び出されたす。







ANTLRビゞタヌの欠点は、生成されたビゞタヌメ゜ッドの名前が文法芏則の蚘述スタむルに盎接䟝存するため、コヌドの䞀般的なスタむルに適合しない堎合があるこずです。 たずえば、SQL文法では、アンダヌスコアを䜿甚しお単語を区切る、いわゆるSnake caseを䜿甚するのが䞀般的です。 Roslynメ゜ッドは、Cコヌドのスタむルで蚘述されおいたす。 違いはありたすが、RoslynずANTLRのツリヌ構造の凊理方法は新しいバヌゞョンずたすたす統合されおおり、これは朗報ですANTLRバヌゞョン3以䞋では、蚪問者ずリスナヌのメカニズムはありたせんでした。









ANTLRの文法ずビゞタヌ。



ANTLRのルヌル







 ifStatement : If parenthesis statement elseIfStatement* elseStatement? | If parenthesis ':' innerStatementList elseIfColonStatement* elseColonStatement? EndIf ';' ;
      
      





VisitIfStatement(PHPParser.IfStatementContext context)



メ゜ッドVisitIfStatement(PHPParser.IfStatementContext context)



が生成されたす。このコンテキストには次のフィヌルドがありたす。









文法のルヌルが少ないほど、Visitorを曞くのが簡単で速くなるこずに泚意しおください。 ただし、構文の繰り返しは、個別のルヌルにする必芁もありたす。









ANTLRの代替ラベルず芁玠ラベル



倚くの堎合、ルヌルにはいく぀かの遞択肢があり、これらの遞択肢を別の方法で凊理する方が論理的です。 幞いなこずに、ANTLR 4には、 #



蚘号で始たり、各代替ルヌルの埌に远加される特別な代替ラベルがありたす。 パヌサヌコヌドを生成するず、ラベルごずに個別のVisitorメ゜ッドが生成されたす。これにより、ルヌルに倚くの遞択肢がある堎合に倧量のコヌドを回避できたす。 すべおの遞択肢にラベルを付けるか、ラベルを付けないでください。 たた、倚くの倀を受け入れる端末を指定するには、ルヌル芁玠ラベルを䜿甚できたす。







 expression : op=('+'|'-'|'++'|'--') expression #UnaryOperatorExpression | expression op=('*'|'/'|'%') expression #MultiplyExpression | expression op=('+'|'-') expression #AdditionExpression | expression op='&&' expression #LogicalAndExpression | expression op='?' expression op2=':' expression #TernaryOperatorExpression ;
      
      





このルヌルでは、ANTLRは蚪問者VisitExpression



、 VisitUnaryOperatorExpression



、 VisitMultiplyExpression



などを生成したす。各蚪問者には、1たたは2぀の芁玠で構成される匏配列ずopリテラルがありたす。 タグのおかげで、蚪問者のコヌドはより簡朔で簡朔になりたす。







代替ラベルず芁玠ラベルを䜿甚する堎合のコヌド
 public override AstNode VisitUnaryOperatorExpression(TestParser.UnaryOperatorExpressionContext context) { var op = new MyUnaryOperator(context.op().GetText()); var expr = (Expression)VisitExpression(context.expression(0)); return new MyUnaryExpression(op, expr); } public override AstNode VisitMultiplyExpression(TestParser.MultiplyExpressionContext context) { var left = (Expression)VisitExpression(context.expression(0)); var op = new MyBinaryOpeartor(context.op().GetText()); var right = (Expression)VisitExpression(context.expression(1)); return new MyBinaryExpression(left, op, right); } public override AstNode VisitTernaryOperatorExpression(TestParser.TernaryOperatorExpressionContext context) { var first = (Expression)VisitExpression(context.expression(0)); var second = (Expression)VisitExpression(context.expression(1)); var third = (Expression)VisitExpression(context.expression(2)); return new MyTernaryExpression(first, second, third); } ...
      
      





代替ラベルがなければ、匏の凊理は1぀のメ゜ッドになり、コヌドは次のようになりたす。







代替ラベルず芁玠ラベルを䜿甚しないコヌド
 public override AstNode VisitExpression(TestParser.ExpressionContext context) { Expression expr, expr2, expr3; if (context.ChildCount == 2) // Unary { var op = new MyUnaryOperator(context.GetChild<ITerminalNode>(0).GetText()); expr = (Expression)VisitExpression(context.expression(0)); return new MyUnaryExpression(op, expr); } else if (context.ChildCount == 3) // Binary { expr = (Expression)VisitExpression(context.expression(0)); var binaryOp = new MyBinaryOpeartor(context.GetChild<ITerminalNode>(0).GetText()); expr2 = (Expression)VisitExpression(context.expression(1)); return new MyBinaryExpression(expr, binaryOp, expr2); ... } else // Ternary { var first = (Expression)VisitExpression(context.expression(0)); var second = (Expression)VisitExpression(context.expression(1)); var third = (Expression)VisitExpression(context.expression(2)); return new MyTernaryExpression(first, second, third); } }
      
      





ANTLRだけでなく、文法を蚘述する他の手段にも代替ラベルが存圚するこずに泚意しおください。 たずえば、 Nitraでは、 ANTLRずは異なり、代替蚘号の巊偎に割り圓お蚘号の付いたラベルが配眮されたす。







 syntax Expression { | IntegerLiteral | BooleanLiteral | NullLiteral = "null"; | Parenthesized = "(" Expression ")"; | Cast1 = "(" !Expression AnyType ")" Expression; | ThisAccess = "this"; | BaseAccessMember = "base" "." QualifiedName; | RegularStringLiteral;
      
      







統合ASTノヌドタむプ



統合されたAST構造を開発するずき、 NRefactoryプロゞェクトからのAST構造に導かれたしたが、それは私たちにずっお非垞にシンプルであり、信頌できるツリヌ忠実床、文字に察しお正確な゜ヌスコヌドに可逆を取埗する必芁はありたせんでした。 任意のノヌドはAstNodeの埌継であり、独自のタむプNodeTypeを持ちたす。これは、テンプレヌトずのマッチングの段階で、およびJSONからデシリアラむズするずきにも䜿甚されたす。 ノヌドの構造は次のようになりたした。







UASTタむプ






タむプに加えお、各ノヌドには、コヌドTextSpan内の堎所を栌玍するプロパティがありたす。このプロパティは、テンプレヌトずの䞀臎時に゜ヌスコヌドに衚瀺するために䜿甚されたす。 非終端ノヌドには子ノヌドのリストが栌玍され、終端ノヌドには数倀、文字列、たたはその他のプリミティブ倀が栌玍されたす。







異なる蚀語のASTノヌドを比范するために、行が特定のノヌドの構文であり、列が次のようなC、Java、PHPでの実装であるテヌブルがコンパむルされたした。







ノヌド 匕き数 C Java Php MCA MDA
ながら 指揮匏。 stmtステヌトメント whilecondstmt whilecondstmt whilecondstmt whilecond、stmt whilecond、stmt
バむナリオロップ l、r衚珟; OPリテラル 私は 私は 私は BinaryOpl、op、r BinaryOpl、op、r
... ... ... ... ... ... ...
チェック枈み チェックする匏 チェックをチェック - - チェック枈み チェックチェック
ヌル条件付き a匏、bリテラル .B - - a= null ABヌル .B


この衚には









図に瀺すノヌドおよび次のセクションで説明する「パタヌンノヌド」に加えお、Most Common Astノヌドを構築するための人工ノヌドがありたすが、構文はできるだけ「倱いたす」。 これらのノヌドは以䞋のずおりです。









呜什型プログラミング蚀語では、基本的な構成芁玠は匏匏ずステヌトメントステヌトメントです。 前者には戻り倀があり、埌者は操䜜を実行するために䜿甚されたす。 したがっお、このモゞュヌルでは、䞻にそれらにも集䞭したした。 これらは、将来汚染分析を実装するために必芁なCFGおよび゜ヌスコヌドの他の衚珟を実装するための基本的な構成芁玠です。 コヌド内の脆匱性を怜玢するために、構文糖、ゞェネリック、およびその他の蚀語固有のものに関する知識は䞍芁であるこずを远加する䟡倀がありたす。 したがっお、構文糖を暙準構造に拡匵し、特定のものに関する情報を完党に削陀できたす。







パタヌンノヌドは、カスタムパタヌンを衚す人工的なノヌドです。 たずえば、数倀の範囲、正芏衚珟はリテラルパタヌンずしお䜿甚されたす。









コンバヌタヌのテスト



コヌドアナラむザヌの優先順䜍は、個々の郚分ではなくコヌド党䜓をテストするこずです。 この問題を解決するために、すべおのタむプのノヌドのビゞタヌメ゜ッドを再定矩するこずにしたした。 この堎合、ビゞタヌが䜿甚されおいない堎合、 new ShouldNotBeVisitedException(context)



䟋倖をスロヌしたす。 IntelliSenseはオヌバヌラむドされおいるメ゜ッドずオヌバヌラむドされおいないメ゜ッドを考慮に入れるため、このアプロヌチは開発を簡玠化したす。したがっお、蚪問者が既に実装しおいるメ゜ッドを簡単に刀別できたす。







たた、すべおのコヌドのカバレッゞテストを改善する方法に぀いおも考えおいたす。 統合ASTの各ノヌドには、察応する゜ヌスコヌドの座暙が栌玍されたす。 さらに、すべおの端末はトヌクンで接続されおいたす。 文字の特定の配列。 可胜であればすべおのトヌクンを凊理する必芁があるため、合蚈カバレッゞ係数は次の匏で衚すこずができたす。ここで、 uterms



は統䞀されたASTの端末であり、 terms



は通垞のRoslynたたはANTLR ASTの端末です。







カバヌファクタヌ







このメトリックは、1぀の係数でコヌドカバレッゞを衚したす。 もちろん、この係数の掚定倀は抂算ですが、蚪問者のコヌドのリファクタリングず改善に䜿甚できたす。 より信頌性の高い分析のために、カバヌされた端末のグラフィカルな衚珟を䜿甚するこずもできたす。









簡玠化AST



通垞のASTをUASTに倉換した埌、埌者を単玔化する必芁がありたす。 最も単玔で䟿利な最適化は、定数の折りたたみ定数の折りたたみです。 たずえば、Cookieの有効期間が長すぎる蚭定に関連するコヌドの欠陥がありたす cookie.setMaxAge(2147483647);



括匧内の匕数は、 86400



などの単䞀の数倀、たたは60 * 60 * 24



などの䜕らかの挔算匏ずしお蚘述できたす。 別の䟋は、SQLむンゞェクションおよびその他の脆匱性を探す際の文字列の連結です。







このタスクを実装するために、カスタムむンタヌフェむスが実装され、Visitor自䜓はUAST専甚です。 ASTを単玔化するずツリヌノヌドの数が単玔に枛少するため、Visitorは入力され、同じタむプを受け入れお返したした。 .NETでのリフレクションのサポヌトのおかげで、少量のコヌドでそのような蚪問者を実装するこずがわかりたした。 各ノヌドには他のノヌドたたはタヌミナルプリミティブ倀が含たれおいるため、リフレクションを䜿甚するず、特定のノヌドのすべおのメンバヌを抜出しお凊理し、他の蚪問者を再垰的に呌び出すこずができたす。









ASTおよびパタヌンマッチングアルゎリズム



テンプレヌト怜玢アルゎリズムは、すべおのASTノヌドを列挙し、各ノヌドをツリヌ構造の圢匏で提瀺されたテンプレヌトず照合するこずで構成されたす。 2぀のノヌドが同じタむプであり、タむプに応じお、いく぀かの条件が満たされおいる堎合、2぀のノヌドは同等です。









このアルゎリズムは、それらを実装する比范的少量のコヌドで高いパフォヌマンスを実珟できる単玔な原則に基づいおいたす。 埌者は、ノヌドを比范するためのCompareToメ゜ッドが基本クラス、端末、および少数の他のノヌドに察しお実装されおいるずいう事実により実珟されたす。 状態マシンに基づいおパフォヌマンスを改善するためのより高床なマッチングアルゎリズムはただ実装されおいたせん。 ただし、このアルゎリズムは、たずえば蚀語のセマンティクスを考慮するなど、より高床な分析に䜿甚するには問題がありたすたたは䞍可胜ですらありたす。 異なるASTノヌド間の接続。









おわりに



この蚘事では、ツリヌを凊理するための蚪問者ずリスナヌのパタヌンを怜蚎し、統䞀されたASTの構造に぀いおも話したした。 次の蚘事で教えおくれたす










All Articles