プログラムの倉曎ず、䜕を倉曎する方がよいかプログラムの実行可胜コヌドたたはAST

このノヌトの原則は、ほずんどすべおのプログラミング蚀語ず実行システムに共通ですが、jvmに重点が眮かれたす。 プログラムを倉曎するには、䞻に2぀のアプロヌチを怜蚎しおください。




メモ内の画像に関連付けられおいるメタファヌプログラムは䞻芁な建物であり、プログラムの倉換の結果は補助的な構造です。

倉曎する理由


プログラムが別のプログラムを倉曎する必芁がある理由を尋ねるこずから始めたしょう。 メタプログラミングは、プロゞェクトの定型コヌドを削枛し、䞻芁なこずに集䞭し、コヌドの読みやすさを向䞊させ、別の方法では解決が困難な問題を解決するのに圹立ちたす。

javaの最も単玔な䟋は、クラスのフィヌルドにアクセスするためのJavaBeansおよびget / setメ゜ッドです。䟋は、クラスのビルダヌの䜜成、IDEでのequals / hashの実装などです。

次の䟋は、ロギング、自動トランザクション管理です。 Spring Frameworkを䜿甚するずきはすべおが䜿甚され、どのように実装されるかに぀いおはほずんど考えたせん。 しかし、Springでさえ、初心者向けのフレヌムワヌクの構成ず初期化に問題が生じ、「魔法の」 Spring Boot / Spring Rooが登堎したした 。 しかし、これは別のトピックですが、プログラムの修正のトピックに戻りたす。

難読化ずコヌドの最小化、蚈装されたプロファむラヌ、DevOpsの䞖界は、プログラムを倉曎する必芁がある理由の3番目の䟋です。 他の重芁な䟋を芋逃したず思うので、コメントを远加しおください。

だから、なぜプログラムを倉曎するのか、私たちは今、圌らが通垞それをどのように行うかを怜蚎するこずにしたした 。

実行可胜プログラム呜什の倉曎


プログラムのバむトコヌドを倉曎できたす。これが最も䞀般的な方法です。 これは、コンパむル盎埌、jarをビルドする前、およびクラスをロヌドするずきに実行できたす。 最初のケヌスでは、プロゞェクトビルドシステムのプラグむン、2番目のケヌスでは、特別なクラスロヌダヌたたはJava゚ヌゞェント、たたはjvmのhotswapメカニズムになりたす。 ゚ヌゞェントずクラスロヌダヌを䜿甚したアプロヌチは、マシンコヌドおよびポリモヌフィックりむルスの自己倉曎プログラムに非垞に䌌おいたす。

jvmがロヌドするファむルのバむトコヌドの構造は、クラス圢匏の公匏ドキュメントに蚘茉されおいたす
javapアプリケヌションを䜿甚するず、コンパむルされたクラスのバむトコヌドを衚瀺できたす
公匏ドキュメントの䟋
クラスの゜ヌスコヌド
import java.awt.*; import java.applet.*; public class DocFooter extends Applet { String date; String email; public void init() { resize(500,100); date = getParameter("LAST_UPDATED"); email = getParameter("EMAIL"); } public void paint(Graphics g) { g.drawString(date + " by ",100, 15); g.drawString(email,290,15); } } 

このクラスのバむトコヌドのコン゜ヌルぞのJavap出力
 Compiled from "DocFooter.java" public class DocFooter extends java.applet.Applet { java.lang.String date; java.lang.String email; public DocFooter(); Code: 0: aload_0 1: invokespecial #1 // Method java/applet/Applet."<init>":()V 4: return public void init(); Code: 0: aload_0 1: sipush 500 4: bipush 100 6: invokevirtual #2 // Method resize:(II)V 9: aload_0 10: aload_0 11: ldc #3 // String LAST_UPDATED 13: invokevirtual #4 // Method getParameter:(Ljava/lang/String;)Ljava/lang/String; 16: putfield #5 // Field date:Ljava/lang/String; 19: aload_0 20: aload_0 21: ldc #6 // String EMAIL 23: invokevirtual #4 // Method getParameter:(Ljava/lang/String;)Ljava/lang/String; 26: putfield #7 // Field email:Ljava/lang/String; 29: return public void paint(java.awt.Graphics); Code: 0: aload_1 1: new #8 // class java/lang/StringBuilder 4: dup 5: invokespecial #9 // Method java/lang/StringBuilder."<init>":()V 8: aload_0 9: getfield #5 // Field date:Ljava/lang/String; 12: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: ldc #11 // String by 17: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 20: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 23: bipush 100 25: bipush 15 27: invokevirtual #13 // Method java/awt/Graphics.drawString:(Ljava/lang/String;II)V 30: aload_1 31: aload_0 32: getfield #7 // Field email:Ljava/lang/String; 35: sipush 290 38: bipush 15 40: invokevirtual #13 // Method java/awt/Graphics.drawString:(Ljava/lang/String;II)V 43: return } 


しかし、バむトコヌドを手動で倉曎するこずは、教育目的たたは困難を䜜成しお克服するのが奜きな忍者である堎合にのみ意味がありたす。 Java Virtual Machine Specificationは、この時点でほずんどの質問に答えおいたす。

産業甚プログラミングでは、 ASM 、 javassist 、 BCEL 、 CGLIBラむブラリがバむトコヌドでの䜜業を簡玠化したす。 バむトコヌドを解析した埌、プログラマは、同じASMを䜿甚しお、ツリヌAPIずビゞタヌテンプレヌトを䜿甚したむベントモデルの䞡方でバむトコヌドを操䜜できたす。 分析に加えお、新しい指瀺、フィヌルド、メ゜ッドなどを倉曎、远加するこずができたす。 本来、バむトコヌドを操䜜するためのラむブラリは他にもありたすが、あたり䜿甚されたせん。

ASM APIの䟋
バむトコヌド分析
 /*** * ASM examples: examples showing how ASM can be used * Copyright (c) 2000-2011 INRIA, France Telecom * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.objectweb.asm.ClassReader; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.IincInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.VarInsnNode; import org.objectweb.asm.tree.analysis.Analyzer; import org.objectweb.asm.tree.analysis.BasicValue; import org.objectweb.asm.tree.analysis.BasicVerifier; import org.objectweb.asm.tree.analysis.Frame; import org.objectweb.asm.tree.analysis.SourceInterpreter; import org.objectweb.asm.tree.analysis.SourceValue; import org.objectweb.asm.util.TraceMethodVisitor; import org.objectweb.asm.util.Textifier; /** * @author Eric Bruneton */ public class Analysis implements Opcodes { public static void main(final String[] args) throws Exception { ClassReader cr = new ClassReader("Analysis"); ClassNode cn = new ClassNode(); cr.accept(cn, ClassReader.SKIP_DEBUG); List<MethodNode> methods = cn.methods; for (int i = 0; i < methods.size(); ++i) { MethodNode method = methods.get(i); if (method.instructions.size() > 0) { if (!analyze(cn, method)) { Analyzer<?> a = new Analyzer<BasicValue>( new BasicVerifier()); try { a.analyze(cn.name, method); } catch (Exception ignored) { } final Frame<?>[] frames = a.getFrames(); Textifier t = new Textifier() { @Override public void visitMaxs(final int maxStack, final int maxLocals) { for (int i = 0; i < text.size(); ++i) { StringBuilder s = new StringBuilder( frames[i] == null ? "null" : frames[i].toString()); while (s.length() < Math.max(20, maxStack + maxLocals + 1)) { s.append(' '); } System.err.print(Integer.toString(i + 1000) .substring(1) + " " + s + " : " + text.get(i)); } System.err.println(); } }; MethodVisitor mv = new TraceMethodVisitor(t); for (int j = 0; j < method.instructions.size(); ++j) { Object insn = method.instructions.get(j); ((AbstractInsnNode) insn).accept(mv); } mv.visitMaxs(0, 0); } } } } /* * Detects unused xSTORE instructions, ie xSTORE instructions without at * least one xLOAD corresponding instruction in their successor instructions * (in the control flow graph). */ public static boolean analyze(final ClassNode c, final MethodNode m) throws Exception { Analyzer<SourceValue> a = new Analyzer<SourceValue>( new SourceInterpreter()); Frame<SourceValue>[] frames = a.analyze(c.name, m); // for each xLOAD instruction, we find the xSTORE instructions that can // produce the value loaded by this instruction, and we put them in // 'stores' Set<AbstractInsnNode> stores = new HashSet<AbstractInsnNode>(); for (int i = 0; i < m.instructions.size(); ++i) { AbstractInsnNode insn = m.instructions.get(i); int opcode = insn.getOpcode(); if ((opcode >= ILOAD && opcode <= ALOAD) || opcode == IINC) { int var = opcode == IINC ? ((IincInsnNode) insn).var : ((VarInsnNode) insn).var; Frame<SourceValue> f = frames[i]; if (f != null) { Set<AbstractInsnNode> s = f.getLocal(var).insns; Iterator<AbstractInsnNode> j = s.iterator(); while (j.hasNext()) { insn = j.next(); if (insn instanceof VarInsnNode) { stores.add(insn); } } } } } // we then find all the xSTORE instructions that are not in 'stores' boolean ok = true; for (int i = 0; i < m.instructions.size(); ++i) { AbstractInsnNode insn = m.instructions.get(i); int opcode = insn.getOpcode(); if (opcode >= ISTORE && opcode <= ASTORE) { if (!stores.contains(insn)) { ok = false; System.err.println("method " + m.name + ", instruction " + i + ": useless store instruction"); } } } return ok; } /* * Test for the above method, with three useless xSTORE instructions. */ public int test(int i, int j) { i = i + 1; // ok, because i can be read after this point if (j == 0) { j = 1; // useless } else { try { j = j - 1; // ok, because j can be accessed in the catch int k = 0; if (i > 0) { k = i - 1; } return k; } catch (Exception e) { // useless ASTORE (e is never used) j = j + 1; // useless } } return 0; } } 

アスペクト指向のアプロヌチは、プログラムを倉曎するための高床な方法ず考えるこずができたす。 ゚ヌゞェント、クラスロヌダヌ、たたはプラグむンレベルでのAspectJ実装では、AOPのすべおの「魔法」がクラスバむトコヌドの操䜜に倉わりたす。 しかし、プログラマが開発䞭にそれを芋るず、同じASMずBCELを䜿甚しお「内郚で」バむトコヌドが倉曎される方法ずは異なりたす。 AspectJが実際にアプリケヌションのクラスに远加するものに興味がある堎合、倉曎されたクラスのダンプを含めお、たずえばJava Decompilerを䜿甚しお、このコヌドをコアに入れるこずができたす 。

AspectJでは、開発者はアクションをクラスの圢匏で定矩し、それらをアスペクトずしお泚釈し、プログラムのどのポむントPointcutで呌び出す必芁があるかを瀺したす。 ポむントカット匏を定矩するための構文も非垞に高床です。 バむトコヌドの倉曎に察するこのアプロヌチは、プログラマにずっお䜿いやすいです。
バむトコヌドの倉曎によるプログラムの倉換には、長所ず短所がありたす。
長所短所
゜ヌスコヌドがなくおもアプロヌチは機胜したす自明でない倉換を䌎う分析ず修正の耇雑さ
コンパむラの独立゜ヌスコヌドでのみ利甚可胜な情報の欠劂

倉換AST゜ヌスコヌド、メタプログラミング


゜ヌスコヌドの倉換の理論ず実践は、プログラミング蚀語のメタプログラミング、Prolog、Lisp、マクロ、およびプリプロセッサで長い間䜿甚されおきたした。

このアプロヌチでは、プログラムの゜ヌスコヌドがコンパむルされる前に別のプログラムによっお倉換たたは補完され、コンパむルされたす。 プログラムテキスト自䜓ではなく、それから構築された抜象的な構文ツリヌ抜象構文ツリヌ、ASTを䜿甚する方が䟿利です。

繰り返したすが、メタプログラミングの利䟿性は、プログラミング蚀語自䜓でのサポヌトに䟝存したす。 冗談がありたす
Lispでは、アスペクト指向プログラミングを探しおいるのであれば、いく぀かのマクロを蚈画するだけで十分です。 Javaでは、Gregor Kichalesが必芁で、新しい䌚瀟を䜜り、䜕ヶ月も䜕幎もすべおを機胜させようずしたす。
ピヌタヌ・ノヌりィグ

したがっお、リフレクションメカニズムは蚀語の䞀郚であり、プラットフォヌムはバむトコヌドを動的にロヌドおよび実行できたすが、jvmはもう少し耇雑です。 コヌド生成を䜿甚する2぀の技術-JPA静的メタモデルゞェネレヌタヌずjaxbコヌド生成-がすぐに思い浮かびたす。 もう1぀の䟋はプロゞェクトLombokです 。これにより、IDEによっお以前に生成されたもの、たたは手動で蚘述され、開発者によっおサポヌトされたものを自動的に実装できたす。
Lombokプロゞェクトの泚釈
val
぀いに 手間のかからない最終ロヌカル倉数。
@ NonNull
たたは心配を止めおNullPointerExceptionを愛するこずを孊んだ方法。
@クリヌンアップ
自動リ゜ヌス管理簡単にcloseメ゜ッドを呌び出したす。
@ゲッタヌ/ @セッタヌ
public int getFoo{return foo;}を再床蚘述しないでください。
@トストリング
フィヌルドを確認するためにデバッガを起動する必芁はありたせんlombokにtoStringを生成させるだけです
@ EqualsAndHashCode
簡単な平等性hashCodeを生成し、オブゞェクトのフィヌルドから実装を同等にしたす。
@ NoArgsConstructor、@ RequiredArgsConstructor、および@ AllArgsConstructor
オヌダヌメむドのコンストラクタヌ匕数を持たないコンストラクタヌ、最終/非NULLフィヌルドごずに1぀の匕数、たたはすべおのフィヌルドごずに1぀の匕数を生成したす。
@デヌタ
すべお䞀緒に@ ToString、@ EqualsAndHashCode、すべおのフィヌルドの@ Getter、およびすべおの非最終フィヌルドの@ Setter、および@ RequiredArgsConstructorのショヌトカット
@倀
䞍倉クラスが非垞に簡単になりたした。
@ビルダヌ
...そしおボブはあなたのおじですオブゞェクト䜜成のための面倒な凝ったパンツAPIはありたせん
@SneakyThrows
これたで誰もスロヌしおいないチェック䟋倖を倧胆にスロヌする
@同期枈み
同期完了ロックを公開しないでください。
@ Getterlazy = true
怠azineは矎埳です
@ログ
船長のログ、24435.7の日付「その行は䜕でしたか」

これは、ASTナヌザヌプログラムずコヌド生成を倉曎するこずでLombokに実装されたす。

制限付きの同様の機胜は、スコヌプコンパむル時の泚釈泚釈凊理ツヌルの javaにもありたす。

Java゜ヌスコヌドを解析する堎合、javacやeclipse javaコンパむラよりも適切に凊理できたす。 SpoonやJTransformerなどの代替手段がありたすが、それらが仕様ず耇雑なクラスをどの皋床完党にサポヌトしおいるかを確認する必芁さえありたせん。

jvmに぀いお話しおいるため、Groovyでのプログラムの゜ヌスコヌドの倉換は蚀語自䜓の䞀郚であり 、 Scala蚀語にも同様の可胜性がありたす。

そのため、プログラムの゜ヌスコヌドの倉曎には匱点ず長所がありたす。
長所短所
バむトコヌドよりも倚くの情報コンパむルたたは解釈段階メモリ、時間
IDEのリファクタリングなどの機胜゜ヌスの可甚性の芁件、特定のクラス/ jarの゜ヌスを自動的に芋぀ける方法


実行時のASTコヌド倉換ず再コンパむル


この投皿の最も筋金入りの郚分は、ランタむムぞの再コンパむルに぀いおの考えです。 束葉杖が必芁な堎合、実行時 に AST Java コヌドの 倉曎ずコンパむルが必芁になる堎合がありたす。プロゞェクトが完党にカプロリットであるか、異なるバヌゞョンの数十のフォヌクを䜜成しお維持するこずが非垞に困難である堎合、たたは管理者ず開発者がそれを理想的で誰にも倉曎を蚱可しない堎合、゜ヌスコヌドぱンタヌプラむズMavenリポゞトリにありたす。 そしお、このアプロヌチが必芁なのは、前述の 2぀のクラスのプログラム倉換で問題を解決するこずが䞍可胜たたは䞍䟿な堎合のみです 。

コンパむルは比范的簡単です。 JavaCompiler APIを䜿甚するず、実行時に゜ヌスコヌドからプログラムをコンパむルし、実装に䟝存しないむンタヌフェむスを提䟛できたす。 eclipse EJCコンパむラヌのマニフェストず゜ヌスコヌドを調べたずころ、JavaCompiler APIもサポヌトしおいるこずがわかりたした。

しかし、プログラムのテキストを分析するずき、ASTで䜜業するためのパブリックおよびナニバヌサルAPIはただありたせん。 ぀たり com.sun.source.tree。*たたはorg.eclipse.jdt.core.domのいずれかず連携する必芁がありたす。

クラスの゜ヌステキストを芋぀けるタスクは、プロゞェクトが゜ヌスタむプのアヌティファクトずずもにmavenリポゞトリで公開され、クラスのあるjarにpom.propertiesたたはpom.xmlファむルがある堎合、たたはアヌティファクトの名前/ハッシュを察応するjarファむルの゜ヌスコヌドに䞀臎させるディクショナリがある堎合、簡単に解決されたすプログラムの実行䞭にこれらの゜ヌスを取埗する方法。

長所-バむトコヌドにあるよりも倚くの情報が倉換に利甚でき、アプリケヌションはプロゞェクトを再組み立おする必芁がなく、AspectJ゚ヌゞェントを䜿甚するのずほが同じくらい䟿利ですが、同時に、バむトコヌド倉換を䜿甚しお倉換を実行するこずはできず、非垞に時間がかかりたす

短所は、以前のアプロヌチず同じですメモリ、時間、プログラムの゜ヌスコヌドの芁件、およびこのクラスでそれを芋぀ける方法。

ejc + mavenコヌドの圢匏での䞊蚘の䟋は今埌数か月のうちに予定されおおり、タスクは非垞に重芁です。 これに遭遇したしたか Javaコヌドを倉換し、実行時に再コンパむルするこずによっおのみ、実践のどのタスクを゚レガントに解決できたすか

ずころで、 TinyCCコンパむラの機胜ずそのサむズは、このアプロヌチがCプログラムでも可胜であるこずを蚌明しおいたす。



このノヌトでは、プログラムを修正するためのいく぀かのアプロヌチ、その長所ず短所を怜蚎したした。 実行可胜コヌドの倉曎はより䞀般的ですが、プログラムの゜ヌスコヌドの存圚ずその埌の倉換なしにすべおのタスクを解決できるわけではありたせん。

Source: https://habr.com/ru/post/J269037/


All Articles