KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > tc > object > bytecode > hook > ClassLoaderPreProcessorImpl


1 /*
2  * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright notice. All rights reserved.
3  */

4 package com.tc.object.bytecode.hook;
5
6 import com.tc.asm.ClassAdapter;
7 import com.tc.asm.ClassReader;
8 import com.tc.asm.ClassVisitor;
9 import com.tc.asm.ClassWriter;
10 import com.tc.asm.Label;
11 import com.tc.asm.MethodAdapter;
12 import com.tc.asm.MethodVisitor;
13 import com.tc.asm.Opcodes;
14 import com.tc.asm.Type;
15
16 import java.util.HashMap JavaDoc;
17 import java.util.Map JavaDoc;
18
19 /**
20  * Instruments the java.lang.ClassLoader to plug in the Class PreProcessor mechanism using ASM. <p/>We are using a lazy
21  * initialization of the class preprocessor to allow all class pre processor logic to be in system classpath and not in
22  * bootclasspath. <p/>This implementation should support IBM custom JRE
23  */

24 public class ClassLoaderPreProcessorImpl {
25
26   private final static String JavaDoc CLASSLOADER_CLASS_NAME = "java/lang/ClassLoader";
27   private final static String JavaDoc DEFINECLASS0_METHOD_NAME = "defineClass0";
28
29   // For JDK5
30
private final static String JavaDoc DEFINECLASS1_METHOD_NAME = "defineClass1";
31   private final static String JavaDoc DEFINECLASS2_METHOD_NAME = "defineClass2";
32
33   private static final String JavaDoc DESC_CORE = "Ljava/lang/String;[BIILjava/security/ProtectionDomain;";
34   private static final String JavaDoc DESC_PREFIX = "(" + DESC_CORE;
35   private static final String JavaDoc DESC_HELPER = "(Ljava/lang/ClassLoader;" + DESC_CORE + ")[B";
36
37   private static final String JavaDoc DESC_BYTEBUFFER_CORE = "Ljava/lang/String;Ljava/nio/ByteBuffer;IILjava/security/ProtectionDomain;";
38   private static final String JavaDoc DESC_BYTEBUFFER_PREFIX = "(" + DESC_BYTEBUFFER_CORE;
39   private static final String JavaDoc DESC_BYTEBUFFER_HELPER = "(Ljava/lang/ClassLoader;" + DESC_BYTEBUFFER_CORE
40                                                          + ")Ljava/nio/ByteBuffer;";
41
42   public ClassLoaderPreProcessorImpl() {
43     //
44
}
45
46   /**
47    * Patch caller side of defineClass0 byte[] weaved = ..hook.impl.ClassPreProcessorHelper.defineClass0Pre(this,
48    * args..); klass = defineClass0(name, weaved, 0, weaved.length, protectionDomain);
49    *
50    * @param classLoaderBytecode
51    * @return
52    */

53   public byte[] preProcess(byte[] classLoaderBytecode) {
54     try {
55       ClassWriter cw = new ClassWriter(true);
56       ClassLoaderVisitor cv = new ClassLoaderVisitor(cw);
57       ClassReader cr = new ClassReader(classLoaderBytecode);
58       cr.accept(cv, false);
59       return cw.toByteArray();
60     } catch (Exception JavaDoc e) {
61       System.err.println("failed to patch ClassLoader:");
62       e.printStackTrace();
63       return classLoaderBytecode;
64     }
65   }
66
67   private static class ClassLoaderVisitor extends ClassAdapter {
68     private String JavaDoc className;
69
70     public ClassLoaderVisitor(ClassVisitor cv) {
71       super(cv);
72     }
73
74     public void visit(int version, int access, String JavaDoc name, String JavaDoc signature, String JavaDoc superName, String JavaDoc[] interfaces) {
75       super.visit(version, access, name, signature, superName, interfaces);
76       this.className = name;
77     }
78
79     public MethodVisitor visitMethod(int access, String JavaDoc name, String JavaDoc desc, String JavaDoc signature, String JavaDoc[] exceptions) {
80       MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
81       if (CLASSLOADER_CLASS_NAME.equals(className) && "loadClassInternal".equals(name)
82           && "(Ljava/lang/String;)Ljava/lang/Class;".equals(desc)) {
83         return new LoadClassVisitor(mv);
84       } else if (CLASSLOADER_CLASS_NAME.equals(className) && "getResource".equals(name)
85                  && "(Ljava/lang/String;)Ljava/net/URL;".equals(desc)) {
86         return new GetResourceVisitor(mv);
87
88       } else {
89         Type[] args = Type.getArgumentTypes(desc);
90         return new ProcessingVisitor(mv, access, args);
91       }
92     }
93   }
94
95   /**
96    * Adding hook for loading tc classes. Uses a primitive state machine to insert new code after first line attribute
97    * (if exists) in order to help with debugging and line-based breakpoints.
98    */

99   public static class GetResourceVisitor extends MethodAdapter implements Opcodes {
100     private boolean isInstrumented = false;
101
102     public GetResourceVisitor(MethodVisitor mv) {
103       super(mv);
104     }
105
106     public void visitLineNumber(int line, Label start) {
107       super.visitLineNumber(line, start);
108       if (!isInstrumented) instrument();
109     }
110
111     public void visitVarInsn(int opcode, int var) {
112       if (!isInstrumented) instrument();
113       super.visitVarInsn(opcode, var);
114     }
115
116     private void instrument() {
117       Label l = new Label();
118
119       mv.visitVarInsn(ALOAD, 1);
120       mv.visitVarInsn(ALOAD, 0);
121       mv.visitMethodInsn(INVOKESTATIC, "com/tc/object/bytecode/hook/impl/ClassProcessorHelper", "getTCResource",
122                          "(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/net/URL;");
123       mv.visitVarInsn(ASTORE, 2);
124
125       mv.visitVarInsn(ALOAD, 2);
126       mv.visitJumpInsn(IFNULL, l);
127
128       mv.visitVarInsn(ALOAD, 2);
129       mv.visitInsn(ARETURN);
130
131       mv.visitLabel(l);
132
133       this.isInstrumented = true;
134     }
135
136   }
137
138   /**
139    * Adding hook for loading tc classes. Uses a primitive state machine to insert new code after first line attribute
140    * (if exists) in order to help with debugging and line-based breakpoints.
141    *
142    * <pre>
143    * mv.visitCode();
144    * Label l0 = new Label();
145    * mv.visitLabel(l0);
146    * mv.visitLineNumber(319, l0);
147    * ... right here
148    * mv.visitVarInsn(ALOAD, 0);
149    * mv.visitVarInsn(ALOAD, 1);
150    * mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/ClassLoader&quot;, &quot;loadClass&quot;, &quot;(Ljava/lang/String;)Ljava/lang/Class;&quot;);
151    * mv.visitInsn(ARETURN);
152    * mv.visitMaxs(2, 2);
153    * mv.visitEnd();
154    * </pre>
155    */

156   public static class LoadClassVisitor extends MethodAdapter implements Opcodes {
157     private boolean isInstrumented = false;
158
159     public LoadClassVisitor(MethodVisitor mv) {
160       super(mv);
161     }
162
163     public void visitLineNumber(int line, Label start) {
164       super.visitLineNumber(line, start);
165       if (!isInstrumented) instrument();
166     }
167
168     public void visitVarInsn(int opcode, int var) {
169       if (!isInstrumented) instrument();
170       super.visitVarInsn(opcode, var);
171     }
172
173     private void instrument() {
174       Label l = new Label();
175
176       mv.visitVarInsn(ALOAD, 1);
177       mv.visitVarInsn(ALOAD, 0);
178       mv.visitMethodInsn(INVOKESTATIC, "com/tc/object/bytecode/hook/impl/ClassProcessorHelper", "getTCClass",
179                          "(Ljava/lang/String;Ljava/lang/ClassLoader;)[B");
180       mv.visitVarInsn(ASTORE, 2);
181
182       mv.visitVarInsn(ALOAD, 2);
183       mv.visitJumpInsn(IFNULL, l);
184
185       mv.visitVarInsn(ALOAD, 0);
186       mv.visitVarInsn(ALOAD, 1);
187       mv.visitVarInsn(ALOAD, 2);
188       mv.visitInsn(ICONST_0);
189       mv.visitVarInsn(ALOAD, 2);
190       mv.visitInsn(ARRAYLENGTH);
191       mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassLoader", "defineClass",
192                          "(Ljava/lang/String;[BII)Ljava/lang/Class;");
193       mv.visitInsn(ARETURN);
194
195       mv.visitLabel(l);
196
197       this.isInstrumented = true;
198     }
199
200   }
201
202   private static class ProcessingVisitor extends RemappingMethodVisitor {
203     public ProcessingVisitor(MethodVisitor cv, int access, Type[] args) {
204       super(cv, access, args);
205     }
206
207     public void visitMethodInsn(int opcode, String JavaDoc owner, String JavaDoc name, String JavaDoc desc) {
208       boolean insertPostCall = false;
209
210       if ((DEFINECLASS0_METHOD_NAME.equals(name) || (DEFINECLASS1_METHOD_NAME.equals(name)))
211           && CLASSLOADER_CLASS_NAME.equals(owner)) {
212         insertPostCall = true;
213         Type[] args = Type.getArgumentTypes(desc);
214         if (args.length < 5 || !desc.startsWith(DESC_PREFIX)) { throw new Error JavaDoc(
215                                                                                 "non supported JDK, native call not supported: "
216                                                                                     + desc); }
217         // store all args in local variables
218
int[] locals = new int[args.length];
219         for (int i = args.length - 1; i >= 0; i--) {
220           mv.visitVarInsn(args[i].getOpcode(Opcodes.ISTORE), locals[i] = nextLocal(args[i].getSize()));
221         }
222         for (int i = 0; i < 5; i++) {
223           mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD), locals[i]);
224         }
225         super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/tc/object/bytecode/hook/impl/ClassProcessorHelper",
226                               "defineClass0Pre", DESC_HELPER);
227         int returnLocalByteArray = nextLocal(args[1].getSize());
228         mv.visitVarInsn(Opcodes.ASTORE, returnLocalByteArray);
229         mv.visitVarInsn(Opcodes.ALOAD, locals[1]); // bytes
230
mv.visitVarInsn(Opcodes.ALOAD, returnLocalByteArray); // new bytes
231
Label l1 = new Label();
232         mv.visitJumpInsn(Opcodes.IF_ACMPEQ, l1);
233         /*
234          * If the return array is same as the input array, then there was no instrumentation done to the class. So
235          * maintain the offsets
236          */

237         mv.visitVarInsn(Opcodes.ALOAD, 0);
238         mv.visitVarInsn(Opcodes.ALOAD, locals[0]); // name
239
mv.visitVarInsn(Opcodes.ALOAD, returnLocalByteArray); // instrumented bytes
240
mv.visitInsn(Opcodes.ICONST_0); // offset
241
mv.visitVarInsn(Opcodes.ALOAD, returnLocalByteArray);
242         mv.visitInsn(Opcodes.ARRAYLENGTH); // length
243
mv.visitVarInsn(Opcodes.ALOAD, locals[4]); // protection domain
244
for (int i = 5; i < args.length; i++) {
245           mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD), locals[i]);
246         }
247         super.visitMethodInsn(opcode, owner, name, desc);
248         Label l2 = new Label();
249         mv.visitJumpInsn(Opcodes.GOTO, l2);
250         mv.visitLabel(l1);
251         mv.visitVarInsn(Opcodes.ALOAD, 0);
252         for (int i = 0; i < args.length; i++) {
253           mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD), locals[i]);
254         }
255         super.visitMethodInsn(opcode, owner, name, desc);
256         mv.visitLabel(l2);
257       } else if (DEFINECLASS2_METHOD_NAME.equals(name) && CLASSLOADER_CLASS_NAME.equals(owner)) {
258         insertPostCall = true;
259         Type[] args = Type.getArgumentTypes(desc);
260         if (args.length < 5 || !desc.startsWith(DESC_BYTEBUFFER_PREFIX)) { throw new Error JavaDoc(
261                                                                                            "non supported JDK, bytebuffer native call not supported: "
262                                                                                                + desc); }
263         // store all args in local variables
264
int[] locals = new int[args.length];
265         for (int i = args.length - 1; i >= 0; i--) {
266           mv.visitVarInsn(args[i].getOpcode(Opcodes.ISTORE), locals[i] = nextLocal(args[i].getSize()));
267         }
268         for (int i = 0; i < 5; i++) {
269           mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD), locals[i]);
270         }
271         super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/tc/object/bytecode/hook/impl/ClassProcessorHelperJDK15",
272                               "defineClass0Pre", DESC_BYTEBUFFER_HELPER);
273         mv.visitVarInsn(Opcodes.ASTORE, locals[1]);
274         mv.visitVarInsn(Opcodes.ALOAD, 0);
275         mv.visitVarInsn(Opcodes.ALOAD, locals[0]); // name
276
mv.visitVarInsn(Opcodes.ALOAD, locals[1]); // bytes
277
mv.visitInsn(Opcodes.ICONST_0); // offset
278
mv.visitVarInsn(Opcodes.ALOAD, locals[1]);
279         mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "Ljava/nio/Buffer;", "remaining", "()I");
280         mv.visitVarInsn(Opcodes.ALOAD, locals[4]); // protection domain
281
for (int i = 5; i < args.length; i++) {
282           mv.visitVarInsn(args[i].getOpcode(Opcodes.ILOAD), locals[i]);
283         }
284         super.visitMethodInsn(opcode, owner, name, desc);
285       } else {
286         super.visitMethodInsn(opcode, owner, name, desc);
287       }
288
289       if (insertPostCall) {
290         super.visitInsn(Opcodes.DUP);
291         super.visitVarInsn(Opcodes.ALOAD, 0);
292         // The newly defined class object should be on the stack at this point
293
super.visitMethodInsn(Opcodes.INVOKESTATIC, "com/tc/object/bytecode/hook/impl/ClassProcessorHelper",
294                               "defineClass0Post", "(Ljava/lang/Class;Ljava/lang/ClassLoader;)V");
295       }
296     }
297   }
298
299   private static class State {
300     Map JavaDoc locals = new HashMap JavaDoc();
301     int firstLocal;
302     int nextLocal;
303
304     State(int access, Type[] args) {
305       nextLocal = ((Opcodes.ACC_STATIC & access) != 0) ? 0 : 1;
306       for (int i = 0; i < args.length; i++) {
307         nextLocal += args[i].getSize();
308       }
309       firstLocal = nextLocal;
310     }
311   }
312
313   private static class IntRef {
314     int key;
315
316     public boolean equals(Object JavaDoc o) {
317       return key == ((IntRef) o).key;
318     }
319
320     public int hashCode() {
321       return key;
322     }
323   }
324
325   private static class RemappingMethodVisitor extends MethodAdapter {
326     private State state;
327     private IntRef check = new IntRef();
328
329     public RemappingMethodVisitor(MethodVisitor v, int access, Type[] args) {
330       super(v);
331       state = new State(access, args);
332     }
333
334     public RemappingMethodVisitor(RemappingMethodVisitor wrap) {
335       super(wrap.mv);
336       this.state = wrap.state;
337     }
338
339     protected int nextLocal(int size) {
340       int var = state.nextLocal;
341       state.nextLocal += size;
342       return var;
343     }
344
345     private int remap(int var, int size) {
346       if (var < state.firstLocal) { return var; }
347       check.key = (size == 2) ? ~var : var;
348       Integer JavaDoc value = (Integer JavaDoc) state.locals.get(check);
349       if (value == null) {
350         IntRef ref = new IntRef();
351         ref.key = check.key;
352         state.locals.put(ref, value = new Integer JavaDoc(nextLocal(size)));
353       }
354       return value.intValue();
355     }
356
357     public void visitIincInsn(int var, int increment) {
358       mv.visitIincInsn(remap(var, 1), increment);
359     }
360
361     public void visitLocalVariable(String JavaDoc name, String JavaDoc desc, String JavaDoc signature, Label start, Label end, int index) {
362       mv.visitLocalVariable(name, desc, signature, start, end, remap(index, 0));
363     }
364
365     public void visitVarInsn(int opcode, int var) {
366       int size;
367       switch (opcode) {
368         case Opcodes.LLOAD:
369         case Opcodes.LSTORE:
370         case Opcodes.DLOAD:
371         case Opcodes.DSTORE:
372           size = 2;
373           break;
374         default:
375           size = 1;
376       }
377       mv.visitVarInsn(opcode, remap(var, size));
378     }
379
380     public void visitMaxs(int maxStack, int maxLocals) {
381       mv.visitMaxs(0, 0);
382     }
383   }
384
385 }
386
Popular Tags