CheckClassAdapter.java revision 674060f01e9090cd21b3c5656cc3204912ad17a6
1/***
2 * ASM: a very small and fast Java bytecode manipulation framework
3 * Copyright (c) 2000-2007 INRIA, France Telecom
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. Neither the name of the copyright holders nor the names of its
15 *    contributors may be used to endorse or promote products derived from
16 *    this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
28 * THE POSSIBILITY OF SUCH DAMAGE.
29 */
30package org.mockito.asm.util;
31
32import java.io.FileInputStream;
33import java.io.PrintWriter;
34import java.util.List;
35
36import org.mockito.asm.AnnotationVisitor;
37import org.mockito.asm.Attribute;
38import org.mockito.asm.ClassAdapter;
39import org.mockito.asm.ClassReader;
40import org.mockito.asm.ClassVisitor;
41import org.mockito.asm.FieldVisitor;
42import org.mockito.asm.MethodVisitor;
43import org.mockito.asm.Opcodes;
44import org.mockito.asm.Type;
45import org.mockito.asm.tree.ClassNode;
46import org.mockito.asm.tree.MethodNode;
47import org.mockito.asm.tree.TryCatchBlockNode;
48import org.mockito.asm.tree.analysis.Analyzer;
49import org.mockito.asm.tree.analysis.Frame;
50import org.mockito.asm.tree.analysis.SimpleVerifier;
51
52/**
53 * A {@link ClassAdapter} that checks that its methods are properly used. More
54 * precisely this class adapter checks each method call individually, based
55 * <i>only</i> on its arguments, but does <i>not</i> check the <i>sequence</i>
56 * of method calls. For example, the invalid sequence
57 * <tt>visitField(ACC_PUBLIC, "i", "I", null)</tt> <tt>visitField(ACC_PUBLIC,
58 * "i", "D", null)</tt>
59 * will <i>not</i> be detected by this class adapter.
60 *
61 * <p><code>CheckClassAdapter</code> can be also used to verify bytecode
62 * transformations in order to make sure transformed bytecode is sane. For
63 * example:
64 *
65 * <pre>
66 *   InputStream is = ...; // get bytes for the source class
67 *   ClassReader cr = new ClassReader(is);
68 *   ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
69 *   ClassVisitor cv = new <b>MyClassAdapter</b>(new CheckClassAdapter(cw));
70 *   cr.accept(cv, 0);
71 *
72 *   StringWriter sw = new StringWriter();
73 *   PrintWriter pw = new PrintWriter(sw);
74 *   CheckClassAdapter.verify(new ClassReader(cw.toByteArray()), false, pw);
75 *   assertTrue(sw.toString(), sw.toString().length()==0);
76 * </pre>
77 *
78 * Above code runs transformed bytecode trough the
79 * <code>CheckClassAdapter</code>. It won't be exactly the same verification
80 * as JVM does, but it run data flow analysis for the code of each method and
81 * checks that expectations are met for each method instruction.
82 *
83 * <p>If method bytecode has errors, assertion text will show the erroneous
84 * instruction number and dump of the failed method with information about
85 * locals and stack slot for each instruction. For example (format is -
86 * insnNumber locals : stack):
87 *
88 * <pre>
89 * org.mockito.asm.tree.analysis.AnalyzerException: Error at instruction 71: Expected I, but found .
90 *   at org.mockito.asm.tree.analysis.Analyzer.analyze(Analyzer.java:289)
91 *   at org.mockito.asm.util.CheckClassAdapter.verify(CheckClassAdapter.java:135)
92 * ...
93 * remove()V
94 * 00000 LinkedBlockingQueue$Itr . . . . . . . .  :
95 *   ICONST_0
96 * 00001 LinkedBlockingQueue$Itr . . . . . . . .  : I
97 *   ISTORE 2
98 * 00001 LinkedBlockingQueue$Itr <b>.</b> I . . . . . .  :
99 * ...
100 *
101 * 00071 LinkedBlockingQueue$Itr <b>.</b> I . . . . . .  :
102 *   ILOAD 1
103 * 00072 <b>?</b>
104 *   INVOKESPECIAL java/lang/Integer.<init> (I)V
105 * ...
106 * </pre>
107 *
108 * In the above output you can see that variable 1 loaded by
109 * <code>ILOAD 1</code> instruction at position <code>00071</code> is not
110 * initialized. You can also see that at the beginning of the method (code
111 * inserted by the transformation) variable 2 is initialized.
112 *
113 * <p>Note that when used like that, <code>CheckClassAdapter.verify()</code>
114 * can trigger additional class loading, because it is using
115 * <code>SimpleVerifier</code>.
116 *
117 * @author Eric Bruneton
118 */
119public class CheckClassAdapter extends ClassAdapter {
120
121    /**
122     * <tt>true</tt> if the visit method has been called.
123     */
124    private boolean start;
125
126    /**
127     * <tt>true</tt> if the visitSource method has been called.
128     */
129    private boolean source;
130
131    /**
132     * <tt>true</tt> if the visitOuterClass method has been called.
133     */
134    private boolean outer;
135
136    /**
137     * <tt>true</tt> if the visitEnd method has been called.
138     */
139    private boolean end;
140
141    /**
142     * Checks a given class. <p> Usage: CheckClassAdapter &lt;fully qualified
143     * class name or class file name&gt;
144     *
145     * @param args the command line arguments.
146     *
147     * @throws Exception if the class cannot be found, or if an IO exception
148     *         occurs.
149     */
150    public static void main(final String[] args) throws Exception {
151        if (args.length != 1) {
152            System.err.println("Verifies the given class.");
153            System.err.println("Usage: CheckClassAdapter "
154                    + "<fully qualified class name or class file name>");
155            return;
156        }
157        ClassReader cr;
158        if (args[0].endsWith(".class")) {
159            cr = new ClassReader(new FileInputStream(args[0]));
160        } else {
161            cr = new ClassReader(args[0]);
162        }
163
164        verify(cr, false, new PrintWriter(System.err));
165    }
166
167    /**
168     * Checks a given class
169     *
170     * @param cr a <code>ClassReader</code> that contains bytecode for the
171     *        analysis.
172     * @param dump true if bytecode should be printed out not only when errors
173     *        are found.
174     * @param pw write where results going to be printed
175     */
176    public static void verify(
177        final ClassReader cr,
178        final boolean dump,
179        final PrintWriter pw)
180    {
181        ClassNode cn = new ClassNode();
182        cr.accept(new CheckClassAdapter(cn), ClassReader.SKIP_DEBUG);
183
184        Type syperType = cn.superName == null
185                ? null
186                : Type.getObjectType(cn.superName);
187        List methods = cn.methods;
188        for (int i = 0; i < methods.size(); ++i) {
189            MethodNode method = (MethodNode) methods.get(i);
190            Analyzer a = new Analyzer(new SimpleVerifier(Type.getObjectType(cn.name),
191                    syperType,
192                    false));
193            try {
194                a.analyze(cn.name, method);
195                if (!dump) {
196                    continue;
197                }
198            } catch (Exception e) {
199                e.printStackTrace(pw);
200            }
201            Frame[] frames = a.getFrames();
202
203            TraceMethodVisitor mv = new TraceMethodVisitor();
204
205            pw.println(method.name + method.desc);
206            for (int j = 0; j < method.instructions.size(); ++j) {
207                method.instructions.get(j).accept(mv);
208
209                StringBuffer s = new StringBuffer();
210                Frame f = frames[j];
211                if (f == null) {
212                    s.append('?');
213                } else {
214                    for (int k = 0; k < f.getLocals(); ++k) {
215                        s.append(getShortName(f.getLocal(k).toString()))
216                                .append(' ');
217                    }
218                    s.append(" : ");
219                    for (int k = 0; k < f.getStackSize(); ++k) {
220                        s.append(getShortName(f.getStack(k).toString()))
221                                .append(' ');
222                    }
223                }
224                while (s.length() < method.maxStack + method.maxLocals + 1) {
225                    s.append(' ');
226                }
227                pw.print(Integer.toString(j + 100000).substring(1));
228                pw.print(" " + s + " : " + mv.buf); // mv.text.get(j));
229            }
230            for (int j = 0; j < method.tryCatchBlocks.size(); ++j) {
231                ((TryCatchBlockNode) method.tryCatchBlocks.get(j)).accept(mv);
232                pw.print(" " + mv.buf);
233            }
234            pw.println();
235        }
236        pw.flush();
237    }
238
239    private static String getShortName(final String name) {
240        int n = name.lastIndexOf('/');
241        int k = name.length();
242        if (name.charAt(k - 1) == ';') {
243            k--;
244        }
245        return n == -1 ? name : name.substring(n + 1, k);
246    }
247
248    /**
249     * Constructs a new {@link CheckClassAdapter}.
250     *
251     * @param cv the class visitor to which this adapter must delegate calls.
252     */
253    public CheckClassAdapter(final ClassVisitor cv) {
254        super(cv);
255    }
256
257    // ------------------------------------------------------------------------
258    // Implementation of the ClassVisitor interface
259    // ------------------------------------------------------------------------
260
261    public void visit(
262        final int version,
263        final int access,
264        final String name,
265        final String signature,
266        final String superName,
267        final String[] interfaces)
268    {
269        if (start) {
270            throw new IllegalStateException("visit must be called only once");
271        }
272        start = true;
273        checkState();
274        checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL
275                + Opcodes.ACC_SUPER + Opcodes.ACC_INTERFACE
276                + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC
277                + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM
278                + Opcodes.ACC_DEPRECATED);
279        if (name == null || !name.endsWith("package-info")) {
280            CheckMethodAdapter.checkInternalName(name, "class name");
281        }
282        if ("java/lang/Object".equals(name)) {
283            if (superName != null) {
284                throw new IllegalArgumentException("The super class name of the Object class must be 'null'");
285            }
286        } else {
287            CheckMethodAdapter.checkInternalName(superName, "super class name");
288        }
289        if (signature != null) {
290            CheckMethodAdapter.checkClassSignature(signature);
291        }
292        if ((access & Opcodes.ACC_INTERFACE) != 0) {
293            if (!"java/lang/Object".equals(superName)) {
294                throw new IllegalArgumentException("The super class name of interfaces must be 'java/lang/Object'");
295            }
296        }
297        if (interfaces != null) {
298            for (int i = 0; i < interfaces.length; ++i) {
299                CheckMethodAdapter.checkInternalName(interfaces[i],
300                        "interface name at index " + i);
301            }
302        }
303        cv.visit(version, access, name, signature, superName, interfaces);
304    }
305
306    public void visitSource(final String file, final String debug) {
307        checkState();
308        if (source) {
309            throw new IllegalStateException("visitSource can be called only once.");
310        }
311        source = true;
312        cv.visitSource(file, debug);
313    }
314
315    public void visitOuterClass(
316        final String owner,
317        final String name,
318        final String desc)
319    {
320        checkState();
321        if (outer) {
322            throw new IllegalStateException("visitOuterClass can be called only once.");
323        }
324        outer = true;
325        if (owner == null) {
326            throw new IllegalArgumentException("Illegal outer class owner");
327        }
328        if (desc != null) {
329            CheckMethodAdapter.checkMethodDesc(desc);
330        }
331        cv.visitOuterClass(owner, name, desc);
332    }
333
334    public void visitInnerClass(
335        final String name,
336        final String outerName,
337        final String innerName,
338        final int access)
339    {
340        checkState();
341        CheckMethodAdapter.checkInternalName(name, "class name");
342        if (outerName != null) {
343            CheckMethodAdapter.checkInternalName(outerName, "outer class name");
344        }
345        if (innerName != null) {
346            CheckMethodAdapter.checkIdentifier(innerName, "inner class name");
347        }
348        checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE
349                + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC
350                + Opcodes.ACC_FINAL + Opcodes.ACC_INTERFACE
351                + Opcodes.ACC_ABSTRACT + Opcodes.ACC_SYNTHETIC
352                + Opcodes.ACC_ANNOTATION + Opcodes.ACC_ENUM);
353        cv.visitInnerClass(name, outerName, innerName, access);
354    }
355
356    public FieldVisitor visitField(
357        final int access,
358        final String name,
359        final String desc,
360        final String signature,
361        final Object value)
362    {
363        checkState();
364        checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE
365                + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC
366                + Opcodes.ACC_FINAL + Opcodes.ACC_VOLATILE
367                + Opcodes.ACC_TRANSIENT + Opcodes.ACC_SYNTHETIC
368                + Opcodes.ACC_ENUM + Opcodes.ACC_DEPRECATED);
369        CheckMethodAdapter.checkIdentifier(name, "field name");
370        CheckMethodAdapter.checkDesc(desc, false);
371        if (signature != null) {
372            CheckMethodAdapter.checkFieldSignature(signature);
373        }
374        if (value != null) {
375            CheckMethodAdapter.checkConstant(value);
376        }
377        FieldVisitor av = cv.visitField(access, name, desc, signature, value);
378        return new CheckFieldAdapter(av);
379    }
380
381    public MethodVisitor visitMethod(
382        final int access,
383        final String name,
384        final String desc,
385        final String signature,
386        final String[] exceptions)
387    {
388        checkState();
389        checkAccess(access, Opcodes.ACC_PUBLIC + Opcodes.ACC_PRIVATE
390                + Opcodes.ACC_PROTECTED + Opcodes.ACC_STATIC
391                + Opcodes.ACC_FINAL + Opcodes.ACC_SYNCHRONIZED
392                + Opcodes.ACC_BRIDGE + Opcodes.ACC_VARARGS + Opcodes.ACC_NATIVE
393                + Opcodes.ACC_ABSTRACT + Opcodes.ACC_STRICT
394                + Opcodes.ACC_SYNTHETIC + Opcodes.ACC_DEPRECATED);
395        CheckMethodAdapter.checkMethodIdentifier(name, "method name");
396        CheckMethodAdapter.checkMethodDesc(desc);
397        if (signature != null) {
398            CheckMethodAdapter.checkMethodSignature(signature);
399        }
400        if (exceptions != null) {
401            for (int i = 0; i < exceptions.length; ++i) {
402                CheckMethodAdapter.checkInternalName(exceptions[i],
403                        "exception name at index " + i);
404            }
405        }
406        return new CheckMethodAdapter(cv.visitMethod(access,
407                name,
408                desc,
409                signature,
410                exceptions));
411    }
412
413    public AnnotationVisitor visitAnnotation(
414        final String desc,
415        final boolean visible)
416    {
417        checkState();
418        CheckMethodAdapter.checkDesc(desc, false);
419        return new CheckAnnotationAdapter(cv.visitAnnotation(desc, visible));
420    }
421
422    public void visitAttribute(final Attribute attr) {
423        checkState();
424        if (attr == null) {
425            throw new IllegalArgumentException("Invalid attribute (must not be null)");
426        }
427        cv.visitAttribute(attr);
428    }
429
430    public void visitEnd() {
431        checkState();
432        end = true;
433        cv.visitEnd();
434    }
435
436    // ------------------------------------------------------------------------
437    // Utility methods
438    // ------------------------------------------------------------------------
439
440    /**
441     * Checks that the visit method has been called and that visitEnd has not
442     * been called.
443     */
444    private void checkState() {
445        if (!start) {
446            throw new IllegalStateException("Cannot visit member before visit has been called.");
447        }
448        if (end) {
449            throw new IllegalStateException("Cannot visit member after visitEnd has been called.");
450        }
451    }
452
453    /**
454     * Checks that the given access flags do not contain invalid flags. This
455     * method also checks that mutually incompatible flags are not set
456     * simultaneously.
457     *
458     * @param access the access flags to be checked
459     * @param possibleAccess the valid access flags.
460     */
461    static void checkAccess(final int access, final int possibleAccess) {
462        if ((access & ~possibleAccess) != 0) {
463            throw new IllegalArgumentException("Invalid access flags: "
464                    + access);
465        }
466        int pub = (access & Opcodes.ACC_PUBLIC) == 0 ? 0 : 1;
467        int pri = (access & Opcodes.ACC_PRIVATE) == 0 ? 0 : 1;
468        int pro = (access & Opcodes.ACC_PROTECTED) == 0 ? 0 : 1;
469        if (pub + pri + pro > 1) {
470            throw new IllegalArgumentException("public private and protected are mutually exclusive: "
471                    + access);
472        }
473        int fin = (access & Opcodes.ACC_FINAL) == 0 ? 0 : 1;
474        int abs = (access & Opcodes.ACC_ABSTRACT) == 0 ? 0 : 1;
475        if (fin + abs > 1) {
476            throw new IllegalArgumentException("final and abstract are mutually exclusive: "
477                    + access);
478        }
479    }
480}
481