あやみつさんの覚書き

メモや覚書きなど

サクラエディタでRubyの強調での単純なミス

Rubyで文字列を扱う際に

"#{name}"

のようにしますよね。そのときになんですが、どうもおかしくて

"#{name}"

のようになってしまいまして、どうしたものかといろいろ探りつつ、サクラエディタRubyの定義ファイルを設定しなおしたりしてもできず、「設定>タイプ別設定>正規表現キーワード」のとこを眺めてたら「正規表現キーワードを使用する(R)」というチェックボックスが。
チェックいれましたところ無事に

"#{name}"

とあいなりました。

おわり

Javaでいいこと思いついた!!(ネタ、Booleanで3つのフラグ)

ネタです。

例えば、あるメソッド内で"これはここでは例外だ"、"その判定を三項演算子で書きたい"という場合です。

あるメソッド、

public void f(Object obj)

があるとします。
objは何型かはわかりませんよね。
そして、以下に示しますが"Class、もしくはStringを継承していないとダメ"といったように"特定のクラスを継承していないと処理をしない"といった場合にしたのような処理で判定できます。

Boolean flag = obj instanceof Class ? judgeClass((Class)obj) : obj instanceof String ? judcgString((String)obj) : null;

といった感じの処理にすれば最大3種類のフラグを得られることがわかりますよね?
objがClassを継承していればjudgeClassのメソッドで判定、objがStringならjudgeStringで判定、それ以外ならnullがflagに格納されます。
ちなみにjudcgClassとjudgeStringはbooleanを返すメソッドです。
うちの環境ではコンパイルエラーはでないようです。

この場合、false,trueは正常な判定がなされ、nullは例外であるという認識で――

別になんということはないですね。

毎回読みづらい文章ですみません。

boolean型のメソッドでbooleanをかえしたい!(asm)

まずMethodInsnNodeをinvoke~でスタックつんでー
InsnNodeのopcodeをireturnでー
おわり!

えっと、少し解説しますね。
opcodeをreturnにしたらWrong~っていわれまして、それで四苦八苦してましたの。
それでですね、実際にbooleanをかえすMethodをのぞいてみましたの。
asmでsysoutしたらreturnにあたる部分にInsnNodeがありましたの。
そのInsnNodeのopcodeがireturnでしたの。
ireturnはintをかえすものですの。
ですの。

つまるところは、intで0か1をかえして判定してるようなのです?
ここら辺はちょっとわからないですが、とりあえず、
booleanかえすときはireturnでintをかえすようにすればいいようです。
おわり。

よくわかってないのでこんな感じにしか書けないです・・・
みづらくてすみません。

未来の自分へ:
booleanかえしたいならireturn使う、それだけはおぼえておきましょう。

asmのLdcInsnNodeについてメモ

LdcInsnNodeはAbstractInsnNodeを継承したクラスで、ClassNodeのmethodsのフィールドの中の、MethodNodeのinstructionsのフィールドの中にはいっていることがあります。
LdcInsnNodeは「LDC」のNodeらしいのですが、この「LDC」が何の略かわからないです。定数がなんたららしいです。
// 追記:2013/02/24
LDCはどうやら"load constant"の略のようです・・・?

LdcInsnNodeはコードの中では

public void f(int i) {
    int j = i + 1;
    System.out.println(j);
}

この中の"1"にあたります。
他にも例を示すならば、

public float f(float f) {
    return f + 1.5F;
}

この中の"1.5F"もそうです。

public boolean f() {
    return true;
}

この中の"true"もそうです。
説明は難しいですが、変数ではないです。

LdcInsnNodeのcstというフィールドは"constant"の略語、つまり定数ということですね。
cstはObject型ですが、出力してみるとInteger型だったりFloat型だったり、プリミティブ型のラッパークラスであるのがわかります。

例えば

public void f() {
    System.out.println(1.5F);
}

この場合はLdcInsnNodeのcstには、Float型の値が1.5のものが入ります。
値だけ変えたいのであればこのcstを置き換えればいいでしょう。

LDCが何の略か知りたいですね・・・

asmでみかける文字列メモ

asmでみかけるIとかVとかをちょこっとまとめ。

メソッドの引数などでみかけるの、プリミティブ型は他にもあるはず
追記:2013/03/15
ASMのFAQのとこにそれに関する記述がありました
ASM - ASM FAQ 7. How do method descriptors work?

文字
V void
Z boolean
C char
B byte
S short
I int
F float
J long
D double
[I int[]
[[Ljava/lang/Object; Object[ ][ ]

メソッドを例にちょっと訳

void foo(String str, int i)

というメソッドがあるとします、これをバイトコード(?)であらわすと

(Ljava/lang/String;I)V

となり、asmでMethodNodeのnameには"foo"、descには"(Ljava/lang/String;I)V"が入ります。
他にコンストラクタを例にすると

public Foo(float f, boolean flag)

C(FZ)V

となり、asmでMethodNodeのnameには"<init>"(半角だと非表示されちゃう・・・)、descには"(FZ)V"が入ります。
コンストラクタは"V"なんですね。

とりあえずいろんなNodeをSystem.out.println()したりしてどういうのが入ってるかを確認していくなり、バイトコード表をみつつクラスファイルをバイナリエディタで確認するなりしてるとなんとなくわかるんじゃないかと思います。
日本語の資料少なめなので英文読むなりjavadoc読むなりで頑張りましょうね。
前も言った気がする。

asmであるクラスのインスタンスを生成する際の処理を置き換える

忘れないうちに書く感じで書くので少し粗いかもしれないですがご了承ください。

今回はあるメソッドの内部のあるクラスのインスタンスを生成する処理を、別のクラスのインスタンスを生成する処理を書きます。

関係したクラス

  • ClassNode
  • ClassReader
  • ClassWriter
  • MethodNode
  • AbstractInsnNode
  • MethodInsnNode
  • TypeInsnNode
  • Opecodes(なくてもいいけどあったほうがいい)

まずは前の記事のようにbyte配列を与えられた際にClassNodeを

public byte[] transform(byte[] arrayOfByte) {
	ClassNode cNode = new ClassNode();
	ClassReader cReader = new ClassReader(arrayOfByte);
	cReader.accept(cNode, 0);// 例によってなんで0かはおいといて
	
	// ここから目当てのMethodNodeを取得します
	// 場合によって目当てのMethodが違うので省きます
	MethodNode targetMNode = null;
	
	// MethodNodeの入ったListをforでまわして、目当てのMethodNodeがみつかればbreak
	for (MethodNode mNode : (List<MethodNode>)cNode.methods) {
		
		// 適当な条件
		if (isTargetMethodNode(mNode)) {
			targetMNode = mNode;
			break;
		}
	}

これでまず目当てのメソッドのMethodNodeを検出します。
次に、そのMethodNodeの内部の処理から目当てのものを検出します。
例えば

Foo foo = new Foo();

のような処理を、Fooを継承したBarというクラスのインスタンスを生成する処理にする場合の

Foo foo = new Bar();

のようにするとします。

検出する方法は、もちろんそれぞれの場合によりますのでご注意を。
今回は「Fooのクラスのインスタンス生成の処理」「Barのクラスのインスタンス生成の処理」に置き換えます。

まず検出する処理を書きましょう。
if文の部分は改行してみやすくしてるつもりです

// nullの確認はしときましょう
if (targetMNode != null) {
	// new を含むTypeInsnNodeと
	// コンストラクタの呼び出しを含むMethodInsnNodeを検出します
	
	// new にあたる部分とコンストラクタの呼び出しに改変を加えます
	// new の部分でもどのコンストラクタを呼ぶかの指定も一部もってるようです
	// それでだいぶつまづきました(java.lang.VerifyError)
	for (AbstractInsnNode aiNode : targetMNode.instructions.toArray()) {
		
		// new する部分の置き換え
		if (aiNode instanceof TypeInsnNode) {
			TypeInsnNode tiNode = (TypeInsnNode)aiNode;
			
			// new するクラスの名前(フルパス)と
			// new をあらわすopcodeで判定します
			// NEW はOpcodesというInterfaceを実装すると参照できます
			if (	   tiNode.desc.equals("Foo")
					&& tiNode.getOpcode() == NEW) {
				tiNode.desc = "Bar";// 置き換え
			}
		}
		
		// コンストラクタ呼び出し部分に改変を加えます
		// コンストラクタはメソッドの1つとして認識されます
		if (aiNode instanceof MethodInsnNode) {
			MethodInsnNode miNode = (MethodInsnNode)aiNode;
			
			// コンストラクタのメソッド名にあたる部分は<init>になります
			// ownerは呼び出しするクラス名(フルパス)でしょうね、たぶん
			// descは引数がなければ()、戻り値にあたる部分はVです。ちなみにVは他のメソッドではvoid型をあらわします
			// getOpcodeでopcodeを取得、INCOKESPECIALはコンストラクタの呼び出しっぽいです
			if (	   miNode.name.equals("<init>")
					&& miNode.owner.equals("Foo")
					&& miNode.desc.equals("()V")
					&& miNode.getOpcode() == INVOKESPECIAL) {
				miNode.owner = "Bar";// 置き換え
			}
		}
	}
}

これで改変を施した状態になりました。
あとはClassWriterでbyte配列にもどしてかえしてあげましょう。

ClassWriter cWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
	cNode.accept(cWriter);
	arrayOfByte = cWriter.toByteArray();
	return arrayOfByte;
}

お疲れ様です、これで終了です。
私はこれのTypeInsnNodeの部分を見落としてjava.lang.VerifyErrorを何度もだしてしにそうでした。

tips:
MethodInsnNodeなどのdescやsignatureを出力してみるとでてくる文字列について

例: (Ljava/lang/String;)V
訳: void型のStringを引数にもつメソッド

こんな感じのを少しでも判別できたらいいかもですね。
Vはvoid、byteはB、intはI、booleanはZなど、他にもありますが調べたらでてきます。
今度またまとめて書いときますか・・・

asm

MOD作成の際にasmに触れる機会に遭遇したので少し覚書きを。
あ、Javaです。


asmの公式ページはこちら→http://asm.ow2.org/
javadocはこちら→http://asm.ow2.org/asm33/javadoc/user/index.html

私が今この記事を書いているときはasmのjavadocは3.3ですが、実際に使用しているバージョンは4.0です。なんででしょ。
今のasmに対する私の印象は「実行時にクラスが読み込まれた時点にそのクラスに改変を施すためのライブラリ」です。長いと印象つきにくいですね。
私自身まだ全然わかっていないのでご了承ください。

今回は簡単な構造についてだけ書きます。

私がやっていた環境ではbyte配列が与えられていました。
そのbyte配列に改変を施すことで実行時のクラスに改変を加え、実際に実行している模様。

まず必要(なのかな?)なの

  • ClassNode
  • ClassReader
  • ClassWriter

流れとしては
ClassReaderにbyte配列いれる

ClassNodeにClassReaderで解析したのをいれてもらう(?)

ClassNodeをいじって改変を加える

ClassWriterにClassNodeいれてbyte配列にもどしてそれをかえす

あってるかわからないですが、現状の私の捉え方はこんな感じです。

それをソースであらわすと

public byte[] transform(byte[] arrayOfByte) {
        ClassNode classNode = new ClassNode();

        // byte配列いれる
        ClassReader classReader = new ClassReader(arrayOfByte);

        // ClassReaderからClassNodeにいれてもらう、0は何かはおいといて
        classReader.accept(classNode, 0);

        //
        // ClassNodeにいろいろして改変を加える
        //

        // COMPUTE_FRAMESとCOMPUTE_MAXSはClassWriterのstatisなフィールド
        // 適宜、参照やstatic importを
        ClassWriter classWriter = new ClassWriter(COMPUTE_FRAMES | COMPUTE_MAXS);

        // ClassNodeからClassWriterにいれてもらう
        classNode.accept(classWriter);

        return classWriter.toByteArray();
}

こんな感じになります。

ClassReader(攻め)×ClassNode(受け)と、
ClassNode(攻め)×ClassWriter(受け)ですね。
わかりやすいですね!ね!

このとき、改変に失敗したりするとjava.lang.VerifyErrorでたりします。
今ちょうどそれに苦しめられてます。しにそうです。

今回はこんな感じで。

[覚えておくこと]

  • ClassReaderは攻め
  • ClassNodeは受けと攻めの両方
  • ClassWriterは受け

・・・さて、英文のソースやjavadoc読んで理解深めましょうねー・・・